library(tidyverse) # for data cleaning and plotting
library(tidymodels) # for modeling ... tidily
library(lubridate) # for date manipulation
library(openintro) # for the abbr2state() function
library(gplots) # for col2hex() function
library(RColorBrewer) # for color palettes
library(ggthemes) # for more themes (including theme_map())
library(plotly) # for the ggplotly() - basic interactivity
library(gganimate) # for adding animation layers to ggplots
library(transformr) # for "tweening" (gganimate)
library(gifski) # need the library for creating gifs but don't need to load each time
library(shiny) # for creating interactive apps
library(janitor)
library(stacks) # for stacking models
library(glmnet) # for regularized regression, including LASSO
library(ranger) # for random forest model
library(kknn) # for knn model
library(naniar) # for examining missing values (NAs)
library(moderndive) # for King County housing data
library(vip) # for variable importance plots
library(rmarkdown) # for paged tables
library(fastDummies)
library(usemodels) # for suggesting step_XXX() functions
library(readr)
library(kableExtra)
library(DALEX) # model Agnostic Language for exploration and explanation (for model interpretation)
library(DALEXtra)
theme_set(theme_minimal())
Introduction
The financial market is a strange place that is very hard to navigate around. We have seen Warren Buffett, Ray Dalio, Charlie Munger - the very very best who have had billions of dollars. Others, 95% of the population, lose the money instead.
So, how are the best of the best pick its stocks? It is from the fundamental, the technical side or the sentimental side? Within this paper, we hope to bring another perspective, using machine learning models to predict the profitability of the stock price.
Paper Outline
First, we will list and explain the definition of each variable in our dataset. There are 25 variables in total as listed below. The variable we are going to predict is PROFIT. Then, we will start to process our data by merging with the macro data and removing some unwanted variables. After processing the data, we will have a data visualization section that shows the distribution and relationship between some variables. And to find the best model, we choose to explore three machine learning models which are LASSO, Random forest and Stacking models. We use a stacking method to create the stacking model by combining three models (Lasso, Random Forest, and KNN). Lastly, we will make a stock return prediction for 2021 using the trained model with the lowest R-squared and RMSE.
List of Variables
For the dataset, we includes financial information on companies in the S&P 500 stock index from 1999-2021. This information was scraped from Yahoo Finance in November of 2021, and collected in a csv format for data analysis. The information includes metrics like sales, earnings, cogs, stock price, and market sector as well as macroeconomic data such as GDP or Money Supply. The goal is to analyze and model this data to better improve projections for a company’s future profitability. The variables in the data set are described below:
| YEAR |
The financial year of the company |
| COMPANY |
The company’s stock abbreviation symbol |
| MARKET.CAP |
The total market capitalization of the company (Volume * Price) |
| EARNINGS |
The earnings in dollars for the previous year for the given company |
| SALES |
How much the company sold in dollars last year |
| CASH |
How much cash the company has in dollars at the end of the previous year |
| Name |
The full name of the company |
| Sector |
The name of the sector that the company is a part of |
| Earnings_next_year |
The amount of money in dollars that the company earns in the following year |
| PRICE |
The price of the stock when it is bought |
| Sell |
The price of the stock when it is sold |
| VOLUME |
The total number of shares that the company holds |
| COGS |
The total amount the company paid as a cost directly related to the sale of products |
| INVESTMENT |
The total asset or item acquired with the goal of generating income or appreciation |
| RECIEVABLE |
The debts owed to a company by its customers for goods that have been delivered or used but not yet paid for |
| INVENTORY |
How much raw materials used in production as well as the goods produced that are available for sale |
| DEBTS |
How much money the company borrow from other parties |
| CPALTT01USM657N_PC1 |
The percentage change in CPI (measure of inflation) |
| GDP |
The monetary value of all finished goods and services made within a country |
| GDP_PC1 |
The percentage change in GDP |
| T10Y2Y |
Ten year treasury bonds minus two year treasury bonds |
| M1SL |
The total currency and other liquid instruments in a country’s economy |
| M1SL_PC1 |
The percentage change in money supply |
| Earnings_next_year |
How much profit that a company produces next year |
| PROFIT |
How much the money made or lost on an investment |
Loading data
finalDATASET <- read_csv("FINALDATASET.csv")
macro_data <- read_csv("clean_macro - Sheet1.csv")
Data Preprocessing
For the data preprocessing, we combined our data sources and filled missing values for some fundamental factors by using the median of that value in the sector for a certain year. We also created sector dummies in this step. We then dropped some variables that are not in our interests and the splited the testing and trainning data.
final_data <- finalDATASET %>%
# make Sector dummy variables
dummy_cols(select_columns = "Sector") %>%
# merge with updated macro data
select(-CPALTT01USM657N_PC1,
-GDP,
-GDP_PC1,
-M1SL_PC1 ,
-M1SL,
-PRICE,
-Sell,
-COMPANY) %>%
merge(macro_data) %>%
# convert Macro factors from characters to numeric
mutate(across(c("CPALTT01USM657N_PC1","GDP","GDP_PC1","T10Y2Y","M1SL_PC1","M1SL"),
as.numeric)) %>%
group_by(YEAR,Sector) %>%
# replacing the missing value with median od the industry in that year
mutate(across(c(DEBTS,INVESTMENTS,CASH,VOLUME,EARNINGS,COGS,SALES,RECEIVABLE,INVENTORY),~replace(.,.==0,median(.)))) %>%
# delete less important factors -> Exchange can possibily be deleted
ungroup() %>%
mutate(across(c(!where(is.numeric),-"Name"),as.factor)) %>%
select(-Earnings_next_year,-observation_date) %>%
drop_na()
# filter out the data for 2021
final_data_2021 <- final_data %>%
filter(YEAR == 2021)
final_data <- final_data %>%
filter(YEAR < 2021)
# split the data
set.seed(327) #for reproducibility
data_split <- initial_split(final_data,
prop = .75)
data_training <- training(data_split)
data_testing <- testing(data_split)
# quick look of the data
final_data %>%
head(5)
Data Visualization
We first explored distributions of our predictors and outcome. As the following graph suggested, all the numeric variables are severely right-skewed with some outliers. In our data, though the numbers of firms in each sector are unbalanced, we got a decent amount of data for each sector.
final_data %>%
select(where(is.numeric)) %>%
select(-starts_with("Sector_")) %>%
pivot_longer(cols = everything(),
names_to = "variable",
values_to = "value") %>%
ggplot(aes(x = value)) +
geom_histogram(bins = 30) +
facet_wrap(vars(variable),
scales = "free",
nrow = 4,
ncol=5)

final_data %>%
select(where(is.factor)) %>%
pivot_longer(cols = everything(),
names_to = "variable",
values_to = "value") %>%
ggplot(aes(x = value)) +
geom_bar() +
facet_wrap(vars(variable),
scales = "free",
nrow = 4) +
theme(axis.text.x = element_text(angle = 45))

Then, we continued to explore the relationship between regressors and the outcome. By observation, we can’t see a strong correlation between fundemental factors, such as investments and debts, and stock return. It may indicate that linear model is not an ideal model in this case.
final_data %>%
ggplot(aes(x = INVESTMENTS, y = PROFIT,color = YEAR)) +
geom_point(alpha = 0.5)+
geom_smooth(se = FALSE) +
labs(title = "Relationship between investments and stock return")

final_data %>%
ggplot(aes(x = DEBTS, y = PROFIT,color = YEAR)) +
geom_point(alpha = 0.5)+
geom_smooth(se = FALSE) +
labs(title = "Relationship between debts and stock return")

With this anamiation, we could see that the returns of stocks follow a cycle, which may be influenced by macroeconomic condition. The return in 1999 and 2019 seems to be the highest for all industries. Also, we noticed that some industries vary a lot year to year, such as IT and healthcare industry.
sector_return_an<-final_data %>%
ggplot(aes(x = PROFIT, y = Sector)) +
geom_boxplot(aes(color = Sector),
alpha = .8,
size = 1) +
labs(title = "Spread of stock return in different sector",
subtitle = "YEAR: {closest_state}",
color = "") +
transition_states(YEAR)
animate(sector_return_an, duration = 25)
anim_save("sector_return.gif")
knitr::include_graphics("sector_return.gif")

We further explored the relationship between the industry and the potential macroeconomic influencer using graphs below. Though the relationship doesn’t seem to be linear, we do see how macroeconomic condition affect each industry differently and will account for that in our model.
final_data %>%
group_by(YEAR,Sector) %>%
mutate(profit_por = PROFIT*`MARKET CAP`/(sum(`MARKET CAP`,na.rm = TRUE))) %>%
summarise(Sector_profit = sum(profit_por),
GDP_PC1 = mean(GDP_PC1),
CPALTT01USM657N_PC1 = mean(CPALTT01USM657N_PC1),
Sector = Sector[1]) %>%
ggplot(aes(x = CPALTT01USM657N_PC1,y = Sector_profit,color = Sector))+
geom_smooth(se = FALSE) +
labs(title = "Relationship between inflation and average return of a industry",
x = "Percent Change in CPI",
y = "Average Return of a sector")

final_data %>%
group_by(YEAR,Sector) %>%
mutate(profit_por = PROFIT*`MARKET CAP`/(sum(`MARKET CAP`,na.rm = TRUE))) %>%
summarise(Sector_profit = sum(profit_por),
GDP_PC1 = mean(GDP_PC1),
CPALTT01USM657N_PC1 = mean(CPALTT01USM657N_PC1),
Sector = Sector[1]) %>%
ggplot(aes(x = GDP_PC1,y = Sector_profit,color = Sector))+
geom_smooth(se = FALSE) +
labs(title = "Relationship between GDP and average return of a industry",
x = "Percent Change in GDP",
y = "Average Return of a sector")

Lasso Model
Building Recipe
In the lasso model, to account for the fact the macroeconomic condition affects each industry differently, we also included the interaction term between GDP and sector dummies.
return_recipe <- recipe(PROFIT ~ ., #short-cut, . = all other vars
data = data_training) %>%
# filter to only have data after 2020
step_filter(YEAR<2021) %>%
step_rm(Name,Sector,YEAR) %>%
#step_rm(GDP,M1SL,Name,Sector,YEAR) %>%
# add PE
step_mutate(PE = `MARKET CAP`/EARNINGS) %>%
# Normalize all variables except for GDP
step_normalize(all_predictors(),
-all_nominal(),
-starts_with("Sector_")) %>%
# Create interaction terms
step_interact(terms = ~c(GDP_PC1):starts_with("Sector_"))
# show the data in recipe
return_recipe %>%
prep(data_training) %>%
# using bake(new_data = NULL) gives same result as juice()
# bake(new_data = NULL)
juice()
Select tuning parameter
return_linear_mod <-
# Define a lasso model
# I believe default is mixture = 1 so probably don't need
linear_reg(mixture = 1) %>%
# Set the engine to "glmnet"
set_engine("glmnet") %>%
# The parameters we will tune.
set_args(penalty = tune()) %>%
# Use "regression"
set_mode("regression")
set.seed(456)
return_lm_wf <-
# Set up the workflow
workflow() %>%
# Add the recipe
add_recipe(return_recipe) %>%
# Add the modeling
add_model(return_linear_mod)
penalty_grid <- grid_regular(penalty(),
levels = 10)
return_cv <- vfold_cv(data_training, v = 5)
return_lm_tune <-
return_lm_wf %>%
tune_grid(
resamples = return_cv,
grid = penalty_grid
)
best_param<-return_lm_tune %>%
select_best(metric = "rmse")
return_lasso_final_wf <- return_lm_wf %>%
finalize_workflow(best_param)
return_lasso_final_mod <- return_lasso_final_wf %>%
fit(data = data_training)
# visulization for best param
set.seed(456)
return_lm_tune %>%
collect_metrics() %>%
filter(.metric == "rmse") %>%
ggplot(aes(x = penalty, y = mean)) +
geom_point() +
geom_line() +
scale_x_log10(
breaks = scales::trans_breaks("log10", function(x) 10^x),
labels = scales::trans_format("log10",scales::math_format(10^.x))) +
labs(x = "penalty", y = "rmse")

Lasso results
The table below shows the estimate of each predictor in the Lasso Model. We can see that indicators in macroeconomics are relatively important to predict the stock return. The change in inflation and GDP all remain significant after shrinking. The interaction terms between GDP and industries also showed importance, accounting for the fact that the macroeconomic condition affects each industry differently: GDP seems to affect the stock return in Communication Services, Energy, and Consumer Discretionary sectors less.
return_lasso_final_mod %>%
pull_workflow_fit() %>%
tidy() %>%
arrange(desc(estimate)) %>%
kbl() %>%
kable_styling(bootstrap_options = c("striped", "bordered", "hover", "condensed")) %>%
scroll_box(width = "100%", height = "500px")
|
term
|
estimate
|
penalty
|
|
M1SL
|
52.2489802
|
0.0059948
|
|
CPALTT01USM657N_PC1
|
22.8362276
|
0.0059948
|
|
GDP_PC1
|
18.5139718
|
0.0059948
|
|
(Intercept)
|
17.4206737
|
0.0059948
|
|
GDP_PC1_x_Sector_Utilities
|
14.5169194
|
0.0059948
|
GDP_PC1_x_Sector_Consumer Staples
|
10.9623359
|
0.0059948
|
GDP_PC1_x_Sector_Health Care
|
10.0994574
|
0.0059948
|
|
Sector_Information Technology
|
9.1062940
|
0.0059948
|
|
T10Y2Y
|
8.3785234
|
0.0059948
|
GDP_PC1_x_Sector_Real Estate
|
7.9131832
|
0.0059948
|
|
Sector_Communication Services
|
7.4629557
|
0.0059948
|
|
Sector_Health Care
|
5.9942110
|
0.0059948
|
|
Sector_Consumer Discretionary
|
4.8412271
|
0.0059948
|
GDP_PC1_x_Sector_Information Technology
|
4.0219608
|
0.0059948
|
|
Sector_Energy
|
2.7587880
|
0.0059948
|
|
COGS
|
2.2012752
|
0.0059948
|
|
GDP_PC1_x_Sector_Industrials
|
2.0052201
|
0.0059948
|
|
VOLUME
|
0.9051955
|
0.0059948
|
|
INVESTMENTS
|
0.4845378
|
0.0059948
|
|
CASH
|
0.4031170
|
0.0059948
|
|
EARNINGS
|
0.3671604
|
0.0059948
|
|
RECEIVABLE
|
0.1688682
|
0.0059948
|
|
INVENTORY
|
0.1677270
|
0.0059948
|
|
GDP_PC1_x_Sector_Financials
|
0.0413482
|
0.0059948
|
|
Sector_Materials
|
0.0000000
|
0.0059948
|
|
PE
|
-0.2304146
|
0.0059948
|
|
DEBTS
|
-0.2444685
|
0.0059948
|
|
Sector_Industrials
|
-0.4250661
|
0.0059948
|
|
GDP_PC1_x_Sector_Materials
|
-1.0239217
|
0.0059948
|
GDP_PC1_x_Sector_Communication Services
|
-1.4192763
|
0.0059948
|
|
Sector_Financials
|
-1.5534082
|
0.0059948
|
|
SALES
|
-3.2886701
|
0.0059948
|
|
Sector_Real Estate
|
-3.3504621
|
0.0059948
|
|
MARKET CAP
|
-3.8678288
|
0.0059948
|
|
Sector_Consumer Staples
|
-5.4154183
|
0.0059948
|
|
GDP_PC1_x_Sector_Energy
|
-5.8821361
|
0.0059948
|
GDP_PC1_x_Sector_Consumer Discretionary
|
-8.6297519
|
0.0059948
|
|
Sector_Utilities
|
-8.7405789
|
0.0059948
|
|
M1SL_PC1
|
-11.4973966
|
0.0059948
|
|
GDP
|
-16.0930330
|
0.0059948
|
Model Evaluation
Prediciton precision
The Lasso model does not perform well. It only explains 20% of the varaince in stock return and it also has a relatively high rmse of around 46.
return_lm_tune %>%
select(id, .metrics) %>%
unnest(.metrics) %>%
filter(.metric == "rsq") %>%
filter(.config == "Preprocessor1_Model08") %>%
summarise(mean_rsq = mean(.estimate))
return_lm_tune %>%
select(id, .metrics) %>%
unnest(.metrics) %>%
filter(.metric == "rmse") %>%
filter(.config == "Preprocessor1_Model08") %>%
summarise(mean_rmse = mean(.estimate))
set.seed(456)
prediction <- predict(
return_lasso_final_mod,
new_data = data_training)
training_pred<-data_training %>%
mutate(.pred = prediction$.pred)
training_pred %>%
ggplot(aes(x = PROFIT,
y = .pred,
color = YEAR)) +
geom_point(alpha = .5,
size = .5) +
geom_smooth(se = FALSE) +
geom_abline(slope = 1,
intercept = 0,
color = "darkred") +
geom_text(aes(label = Name),label.size = 0.15,data = training_pred %>% filter(PROFIT>400) )+
labs(x = "Actual Return",
y = "Predicted Return") +
scale_color_viridis_b()
Overfitting
Below shows the r-square and rmse on testing data. They are very similar to those on trainning data so we would safely conclude that our model did not overfit.
return_lasso_test <- return_lasso_final_wf %>%
last_fit(data_split)
# Metrics for model applied to test data
return_lasso_test %>%
collect_metrics()
The following graphs show the performance of the model on the testing data:
test_prediction <- predict(
return_lasso_final_mod,
new_data = data_testing)
testing_lasso_pred<-data_testing %>%
mutate(.pred = test_prediction$.pred)
testing_lasso_pred %>%
ggplot(aes(x = PROFIT,
y = .pred,
color = YEAR)) +
geom_point(alpha = .5,
size = .5) +
geom_smooth(se = FALSE) +
geom_abline(slope = 1,
intercept = 0,
color = "darkred") +
geom_text(aes(label = Name),label.size = 0.15,data = testing_lasso_pred %>% filter(PROFIT>300) )+
labs(x = "Actual Return",
y = "Predicted Return") +
scale_color_viridis_b()

Interpretable Machine Learning
Again, the boxplot and the histogram of residuals, the model does not predict the return well and this may because some extreme values.
lasso_explain <-
explain_tidymodels(
model = return_lasso_final_mod,
data = data_training %>% select(-PROFIT),
y = data_training %>% pull(PROFIT),
label = "lasso"
)
## Preparation of a new explainer is initiated
## -> model label : lasso
## -> data : 7172 rows 30 cols
## -> data : tibble converted into a data.frame
## -> target variable : 7172 values
## -> predict function : yhat.workflow will be used ( [33m default [39m )
## -> predicted values : No value for predict function target column. ( [33m default [39m )
## -> model_info : package tidymodels , ver. 0.1.3 , task regression ( [33m default [39m )
## -> predicted values : numerical, min = -49.61277 , mean = 19.22262 , max = 121.2461
## -> residual function : difference between y and yhat ( [33m default [39m )
## -> residuals : numerical, min = -146.2719 , mean = -4.401566e-14 , max = 1002.161
## [32m A new explainer has been created! [39m
lasso_mod_perf <- model_performance(lasso_explain)
hist_plot <-
plot(lasso_mod_perf,
geom = "histogram")
box_plot <-
plot(lasso_mod_perf,
geom = "boxplot")
hist_plot

box_plot

Below shows the feature importance plots generated by two different methods. We could see that macroeconomic indicators remained importance in both of the plots but market cap seems to be an crucial factor when we use permutation method.
set.seed(10) #since we are sampling & permuting, we set a seed so we can replicate the results
lasso_var_imp <-
model_parts(
lasso_explain
)
plot(lasso_var_imp, show_boxplots = TRUE)

return_lasso_final_mod %>%
pull_workflow_fit() %>%
vip()

Random Forest Model
Building Random Forest model
To build the random forest model, we set up recipe using our training data (data_training), define model with mtry = 6, min_n = 10 and numbers of tree = 200, create ranger workflow, and then we can fit the model.
# set up recipe
ranger_recipe <-
recipe(PROFIT ~ ., #short-cut, . = all other vars
data = data_training) %>%
step_filter(YEAR<2021) %>%
# remove the unwanted variables
step_rm(YEAR,Name,GDP,M1SL,Sector) %>%
# add PE
step_mutate(PE = `MARKET CAP`/EARNINGS)
#define model
ranger_spec <-
rand_forest(mtry = 6,
min_n = 10,
trees = 200) %>%
set_mode("regression") %>%
set_engine("ranger")
#create workflow
ranger_workflow <-
workflow() %>%
add_recipe(ranger_recipe) %>%
add_model(ranger_spec)
#fit the model
set.seed(712) # for reproducibility - random sampling in random forest choosing number of variables
ranger_fit <- ranger_workflow %>%
fit(data_training)
Random Forest results
The table below shows the OOB error (MSE), OOB RMSE (Root mean square error), and R squared of the stock return predictions using the random forest model above.
OOB error:
ranger_fit$fit$fit$fit$prediction.error
## [1] 1545.135
OOB RMSE:
sqrt(ranger_fit$fit$fit$fit$prediction.error)
## [1] 39.3082
R Squared:
ranger_fit$fit$fit$fit$r.squared
## [1] 0.4086307
Model Evaluation
Prediciton precision (Traning data)
Even though the mean rmse from the random forest (39.30787) seems to be fairly high but it is still lower than the LASSO model of 46. Thus, random forest model performs better.
set.seed(1211) # for reproducibility
data_cv <- vfold_cv(data_training, v = 5)
metric <- metric_set(rmse)
ctrl_res <- control_stack_resamples()
ranger_cv <- ranger_workflow %>%
fit_resamples(data_cv,
metrics = metric,
control = ctrl_res)
# Evaluation metrics averaged over all folds:
collect_metrics(ranger_cv)
We also plot a graph showing the actual return vs predicted return.
ranger_prediction <- predict(
ranger_fit,
new_data = data_training)
ranger_training_pred<-data_training %>%
mutate(.pred = ranger_prediction$.pred)
ranger_training_pred %>%
ggplot(aes(x = PROFIT,
y = .pred)) +
geom_point(alpha = .5,
size = .5) +
geom_smooth(se = FALSE) +
geom_abline(slope = 1,
intercept = 0,
color = "darkred") +
labs(x = "Actual Return",
y = "Predicted Return")

Prediciton precision (Testing data)
The table below shows the rmse on testing data. It is a bit higher but fairly similar to the error on training data so we could say that our model did not overfit.
set.seed(1211) # for reproducibility
data_cv <- vfold_cv(data_testing, v = 5)
metric <- metric_set(rmse)
ctrl_res <- control_stack_resamples()
ranger_cv <- ranger_workflow %>%
fit_resamples(data_cv,
metrics = metric,
control = ctrl_res)
# Evaluation metrics averaged over all folds:
collect_metrics(ranger_cv)
Below is the graph showing the actual return vs. predicted return on testing data.
ranger_test_prediction <- predict(
ranger_fit,
new_data = data_testing)
ranger_test_pred<-data_testing %>%
mutate(.pred = ranger_test_prediction$.pred)
ranger_test_pred %>%
ggplot(aes(x = PROFIT,
y = .pred)) +
geom_point(alpha = .5,
size = .5) +
geom_smooth(se = FALSE) +
geom_abline(slope = 1,
intercept = 0,
color = "darkred") +
labs(x = "Actual Return",
y = "Predicted Return")

Interpretable Machine Learning
Based on the box-plot and the histogram below, the residuals mostly lie between -50 to 50. But there are some a few outliners that can go up to 400.
rf_explain <-
explain_tidymodels(
model = ranger_fit,
data = data_training %>% select(-PROFIT),
y = data_training %>% pull(PROFIT),
label = "rf"
)
## Preparation of a new explainer is initiated
## -> model label : rf
## -> data : 7172 rows 30 cols
## -> data : tibble converted into a data.frame
## -> target variable : 7172 values
## -> predict function : yhat.workflow will be used ( [33m default [39m )
## -> predicted values : No value for predict function target column. ( [33m default [39m )
## -> model_info : package tidymodels , ver. 0.1.3 , task regression ( [33m default [39m )
## -> predicted values : numerical, min = -76.57776 , mean = 19.57231 , max = 438.0276
## -> residual function : difference between y and yhat ( [33m default [39m )
## -> residuals : numerical, min = -101.4695 , mean = -0.3496883 , max = 599.5611
## [32m A new explainer has been created! [39m
rf_mod_perf <- model_performance(rf_explain)
hist_plot <-
plot(rf_mod_perf,
geom = "histogram")
box_plot <-
plot(rf_mod_perf,
geom = "boxplot")
hist_plot

box_plot

According to the feature importance bar chart below, we can see that the top three important features are market cap, earnings, M1SL_PC1, and GDP_PC1.
set.seed(10) #since we are sampling & permuting, we set a seed so we can replicate the results
rf_var_imp <-
model_parts(
rf_explain
)
plot(rf_var_imp, show_boxplots = TRUE)

After creating two models, we then move on to the final method: stacking. For the stacking model, we also add one more model: KNN to improve the accuracy rate of the model.
Stacking Model
Random Forest Candidate
ranger_recipe <-
recipe(formula = PROFIT ~ .,
data = data_training) %>%
# Make these evaluative variables, not included in modeling
update_role(all_of(c("YEAR", "Name", "Sector")),
new_role = "evaluative")
ranger_spec <-
rand_forest(mtry = 6,
min_n = 10,
trees = 200) %>%
set_mode("regression") %>%
set_engine("ranger")
ranger_workflow <-
workflow() %>%
add_recipe(ranger_recipe) %>%
add_model(ranger_spec)
ranger_fit <- ranger_workflow %>%
fit(data_training)
set.seed(1211) # for reproducibility
final_data_cv <- vfold_cv(data_training, v = 5)
metric <- metric_set(rmse)
ctrl_res <- control_stack_resamples()
ranger_cv <- ranger_workflow %>%
fit_resamples(final_data_cv,
metrics = metric,
control = ctrl_res)
LASSO Candiate
# lasso recipe and transformation steps
lasso_final_data_recipe <- recipe(PROFIT ~ .,
data = data_training) %>%
#step_rm(Name, Sector, YEAR, COMPANY) %>%
update_role(all_of(c("Name",
"Sector",
"YEAR")),
new_role = "evaluative") %>%
step_dummy(all_nominal(),
-all_outcomes(),
-has_role(match = "evaluative")) %>%
step_normalize(all_predictors(),
-all_nominal())
#define lasso model
lasso_mod <-
linear_reg(mixture = 1) %>%
set_engine("glmnet") %>%
set_args(penalty = tune()) %>%
set_mode("regression")
# create workflow
lasso_wf <-
workflow() %>%
add_recipe(lasso_final_data_recipe) %>%
add_model(lasso_mod)
# penalty grid - changed to 10 levels
penalty_grid <- grid_regular(penalty(),
levels = 10)
# add ctrl_grid - assures predictions and workflows are saved
ctrl_grid <- control_stack_grid()
# tune the model using the same cv samples as random forest
lasso_tune <-
lasso_wf %>%
tune_grid(
resamples = final_data_cv,
grid = penalty_grid,
metrics = metric,
control = ctrl_grid
)
KNN Candidate
# create a model definition
knn_mod <-
nearest_neighbor(
neighbors = tune("k")
) %>%
set_engine("kknn") %>%
set_mode("regression")
# create the workflow
knn_wf <-
workflow() %>%
add_model(knn_mod) %>%
add_recipe(lasso_final_data_recipe)
# tune it using 4 tuning parameters
knn_tune <-
knn_wf %>%
tune_grid(
final_data_cv,
metrics = metric,
grid = 4,
control = ctrl_grid
)
Stacking all candidates
By showing and visualize the members in our stacking model, we could see that random forest model contribute the most.
final_data_stack <-
stacks() %>%
add_candidates(ranger_cv) %>%
add_candidates(lasso_tune) %>%
add_candidates(knn_tune)
final_data_blend <-
final_data_stack %>%
blend_predictions()
final_data_blend
## # A tibble: 2 x 3
## member type weight
## <chr> <chr> <dbl>
## 1 ranger_cv_1_1 rand_forest 0.835
## 2 knn_tune_1_4 nearest_neighbor 0.254
autoplot(final_data_blend)

final_data_blend$metrics %>%
filter(.metric == "rmse") %>%
summarise(mean_rmse = mean(mean))
final_data_final_stack <- final_data_blend %>%
fit_members()
Table below shows the top10 prediction when we applied the model to testing data. Here, we can see that when the model predicts company with high rate of return, the actual return is high as well.
final_data_final_stack %>%
predict(new_data = data_testing) %>%
bind_cols(data_testing) %>%
select(Name, .pred, PROFIT) %>%
arrange(desc(.pred)) %>%
head(10)
Comparison of the three models
With the three models, we then move on to see which model performs the best:
Lasso model
data_frame(return_lm_tune %>%
select(id, .metrics) %>%
unnest(.metrics) %>%
filter(.metric == "rsq") %>%
filter(.config == "Preprocessor1_Model08") %>%
summarise(mean_rsq = mean(.estimate)),
return_lm_tune %>%
select(id, .metrics) %>%
unnest(.metrics) %>%
filter(.metric == "rmse") %>%
filter(.config == "Preprocessor1_Model08") %>%
summarise(mean_rmse = mean(.estimate)))
Random Forest Model
#OOB RMSE
data.frame(mean_rmse = sqrt(ranger_fit$fit$fit$fit$prediction.error),
# R squared
mean_rsq = ranger_fit$fit$fit$fit$r.squared)
Stacking Model
# Stacking model:
data.frame(final_data_blend$metrics %>%
filter(.metric == "rmse") %>%
summarise(mean_rmse = mean(mean)),
final_data_blend$metrics %>%
filter(.metric == "rsq") %>%
summarise(mean_rsq = mean(mean)))
Here, we can see that compared to the three models, even though Random Forest performs better than the stacking model, stacking model use features in the Random Forest along with additional features from KNN and lasso. With that reason, we will choose stacking model as our model choice.
Stock Return Prediction for 2021
After picking our model, we then move on to use the model to predict the potential profit for 2021:
set.seed(456)
ytd = c(80.30, 39.77, -8.26, 11.29, 22.08, 132.51, 137.04, -4.22, 39.77, 20.76, 26.41, 26.34, 41.50, 178.43, 18.70, 87.29, -6.16, 52.58, 8.70, -13.70)
set.seed(456)
pred_2021<-final_data_final_stack %>%
predict(new_data = final_data_2021) %>%
bind_cols(final_data_2021) %>%
select(Name, .pred) %>%
arrange(desc(.pred)) %>%
head(20) %>%
mutate(actual_ytd = ytd)
pred_2021 %>%
kbl() %>%
kable_styling(bootstrap_options = c("striped", "bordered", "hover", "condensed")) %>%
column_spec(c(1,3), color = ifelse(pred_2021$actual_ytd < 0, "red", "darkgreen")) %>%
scroll_box(width = "100%", height = "500px")
|
Name
|
.pred
|
actual_ytd
|
|
APA Corporation
|
67.67959
|
80.30
|
|
Marathon Oil
|
63.13998
|
39.77
|
|
News Corp (Class B)
|
63.06597
|
-8.26
|
|
Under Armour (Class C)
|
62.82090
|
11.29
|
|
DuPont
|
62.70736
|
22.08
|
|
Ford
|
62.55623
|
132.51
|
|
Norwegian Cruise Line Holdings
|
62.50470
|
137.04
|
|
Under Armour (Class A)
|
61.68342
|
-4.22
|
|
Carnival Corporation
|
61.52755
|
39.77
|
|
DXC Technology
|
59.89012
|
20.76
|
|
Halliburton
|
59.45987
|
26.41
|
|
Raytheon Technologies
|
58.35294
|
26.34
|
|
Devon Energy
|
57.31403
|
41.50
|
|
Schlumberger
|
57.14758
|
178.43
|
|
ConocoPhillips
|
56.75287
|
18.70
|
|
Baker Hughes
|
56.39542
|
87.29
|
|
Hess Corporation
|
56.39404
|
-6.16
|
|
Southwest Airlines
|
56.14967
|
52.58
|
|
Bristol Myers Squibb
|
56.04499
|
8.70
|
|
Boeing
|
55.79926
|
-13.70
|
Conclusion
The weakness of our model is that the average error of prediction is still very high so if one wants to predict the exact return, our model won’t be ideal. But the strength of our model is that for stocks with extremely high returns, even though the prediction might not be that precise, highly likely, our model will predict positive returns. That means, in real life, if we choose the top stocks to invest in based on our prediction, it is less likely we are going to lose money. Another strength of using a model to help with investment is that it excludes our subjective feelings.
To make the model better, we could do more research and add more regressors. For example, some financial indicators that are important for value investing are not reflected in our model due to the lack of data. Examples of those variables include Price to Sales, Price to Cash Flow, and Price to Book. Also, since we are concerned about the long-term return and all fundamental factors usually take longer to affect the firms, it’s probably helpful to do return in 2 years or 3 years or include lag of some variables in our model.
LS0tCnRpdGxlOiAiRmluYWwgUHJvamVjdCIKYXV0aG9yOiAiRHVjIE5nbywgUml0YSBMaXUsIFNpdmh1byBQcmFrIgpvdXRwdXQ6CiAgaHRtbF9kb2N1bWVudDoKICAgIGRmX3ByaW50OiBwYWdlZAogICAgdG9jOiBUUlVFCiAgICB0b2NfZmxvYXQ6IFRSVUUKICAgIGNvZGVfZG93bmxvYWQ6IHRydWUKICAgIGNvZGVfZm9sZGluZzogaGlkZQogICAgdGhlbWU6IGpvdXJuYWwKLS0tCgpgYGB7ciBzZXR1cCwgaW5jbHVkZT1GQUxTRX0Ka25pdHI6Om9wdHNfY2h1bmskc2V0KGVjaG8gPSBUUlVFLCBtZXNzYWdlPUZBTFNFLCB3YXJuaW5nPUZBTFNFKQpgYGAKCmBgYHtyfQpsaWJyYXJ5KHRpZHl2ZXJzZSkgICAgICAgICMgZm9yIGRhdGEgY2xlYW5pbmcgYW5kIHBsb3R0aW5nCmxpYnJhcnkodGlkeW1vZGVscykgICAgICAgIyBmb3IgbW9kZWxpbmcgLi4uIHRpZGlseQpsaWJyYXJ5KGx1YnJpZGF0ZSkgICAgICAgICMgZm9yIGRhdGUgbWFuaXB1bGF0aW9uCmxpYnJhcnkob3BlbmludHJvKSAgICAgICAgIyBmb3IgdGhlIGFiYnIyc3RhdGUoKSBmdW5jdGlvbgpsaWJyYXJ5KGdwbG90cykgICAgICAgICAgICMgZm9yIGNvbDJoZXgoKSBmdW5jdGlvbgpsaWJyYXJ5KFJDb2xvckJyZXdlcikgICAgICMgZm9yIGNvbG9yIHBhbGV0dGVzCmxpYnJhcnkoZ2d0aGVtZXMpICAgICAgICAgIyBmb3IgbW9yZSB0aGVtZXMgKGluY2x1ZGluZyB0aGVtZV9tYXAoKSkKbGlicmFyeShwbG90bHkpICAgICAgICAgICAjIGZvciB0aGUgZ2dwbG90bHkoKSAtIGJhc2ljIGludGVyYWN0aXZpdHkKbGlicmFyeShnZ2FuaW1hdGUpICAgICAgICAjIGZvciBhZGRpbmcgYW5pbWF0aW9uIGxheWVycyB0byBnZ3Bsb3RzCmxpYnJhcnkodHJhbnNmb3JtcikgICAgICAgIyBmb3IgInR3ZWVuaW5nIiAoZ2dhbmltYXRlKQpsaWJyYXJ5KGdpZnNraSkgICAgICAgICAgICMgbmVlZCB0aGUgbGlicmFyeSBmb3IgY3JlYXRpbmcgZ2lmcyBidXQgZG9uJ3QgbmVlZCB0byBsb2FkIGVhY2ggdGltZQpsaWJyYXJ5KHNoaW55KSAgICAgICAgICAgICMgZm9yIGNyZWF0aW5nIGludGVyYWN0aXZlIGFwcHMKbGlicmFyeShqYW5pdG9yKSAgCmxpYnJhcnkoc3RhY2tzKSAgICAgICAgICAgICMgZm9yIHN0YWNraW5nIG1vZGVscwpsaWJyYXJ5KGdsbW5ldCkgICAgICAgICAgICAjIGZvciByZWd1bGFyaXplZCByZWdyZXNzaW9uLCBpbmNsdWRpbmcgTEFTU08KbGlicmFyeShyYW5nZXIpICAgICAgICAgICAgIyBmb3IgcmFuZG9tIGZvcmVzdCBtb2RlbApsaWJyYXJ5KGtrbm4pICAgICAgICAgICAgICAjIGZvciBrbm4gbW9kZWwKbGlicmFyeShuYW5pYXIpICAgICAgICAgICAgIyBmb3IgZXhhbWluaW5nIG1pc3NpbmcgdmFsdWVzIChOQXMpCmxpYnJhcnkobW9kZXJuZGl2ZSkgICAgICAgICMgZm9yIEtpbmcgQ291bnR5IGhvdXNpbmcgZGF0YQpsaWJyYXJ5KHZpcCkgICAgICAgICAgICAgICAjIGZvciB2YXJpYWJsZSBpbXBvcnRhbmNlIHBsb3RzCmxpYnJhcnkocm1hcmtkb3duKSAgICAgICAgICMgZm9yIHBhZ2VkIHRhYmxlcwpsaWJyYXJ5KGZhc3REdW1taWVzKQpsaWJyYXJ5KHVzZW1vZGVscykgICAgICAgICAjIGZvciBzdWdnZXN0aW5nIHN0ZXBfWFhYKCkgZnVuY3Rpb25zCmxpYnJhcnkocmVhZHIpCmxpYnJhcnkoa2FibGVFeHRyYSkKbGlicmFyeShEQUxFWCkgICAgICAgICAgICAgIyBtb2RlbCBBZ25vc3RpYyBMYW5ndWFnZSBmb3IgZXhwbG9yYXRpb24gYW5kIGV4cGxhbmF0aW9uIChmb3IgbW9kZWwgaW50ZXJwcmV0YXRpb24pICAKbGlicmFyeShEQUxFWHRyYSkKdGhlbWVfc2V0KHRoZW1lX21pbmltYWwoKSkKYGBgCgoKIyMgSW50cm9kdWN0aW9uIAoKVGhlIGZpbmFuY2lhbCBtYXJrZXQgaXMgYSBzdHJhbmdlIHBsYWNlIHRoYXQgaXMgdmVyeSBoYXJkIHRvIG5hdmlnYXRlIGFyb3VuZC4gV2UgaGF2ZSBzZWVuIFdhcnJlbiBCdWZmZXR0LCBSYXkgRGFsaW8sIENoYXJsaWUgTXVuZ2VyIC0gdGhlIHZlcnkgdmVyeSBiZXN0IHdobyBoYXZlIGhhZCBiaWxsaW9ucyBvZiBkb2xsYXJzLiBPdGhlcnMsIDk1JSBvZiB0aGUgcG9wdWxhdGlvbiwgbG9zZSB0aGUgbW9uZXkgaW5zdGVhZC4gCgpTbywgaG93IGFyZSB0aGUgYmVzdCBvZiB0aGUgYmVzdCBwaWNrIGl0cyBzdG9ja3M/IEl0IGlzIGZyb20gdGhlIGZ1bmRhbWVudGFsLCB0aGUgdGVjaG5pY2FsIHNpZGUgb3IgdGhlIHNlbnRpbWVudGFsIHNpZGU/IFdpdGhpbiB0aGlzIHBhcGVyLCB3ZSBob3BlIHRvIGJyaW5nIGFub3RoZXIgcGVyc3BlY3RpdmUsIHVzaW5nIG1hY2hpbmUgbGVhcm5pbmcgbW9kZWxzIHRvIHByZWRpY3QgdGhlIHByb2ZpdGFiaWxpdHkgb2YgdGhlIHN0b2NrIHByaWNlLiAKCiMjIyBQYXBlciBPdXRsaW5lIAoKRmlyc3QsIHdlIHdpbGwgbGlzdCBhbmQgZXhwbGFpbiB0aGUgZGVmaW5pdGlvbiBvZiBlYWNoIHZhcmlhYmxlIGluIG91ciBkYXRhc2V0LiBUaGVyZSBhcmUgMjUgdmFyaWFibGVzIGluIHRvdGFsIGFzIGxpc3RlZCBiZWxvdy4gVGhlIHZhcmlhYmxlIHdlIGFyZSBnb2luZyB0byBwcmVkaWN0IGlzIGBQUk9GSVRgLiBUaGVuLCB3ZSB3aWxsIHN0YXJ0IHRvIHByb2Nlc3Mgb3VyIGRhdGEgYnkgbWVyZ2luZyB3aXRoIHRoZSBtYWNybyBkYXRhIGFuZCByZW1vdmluZyBzb21lIHVud2FudGVkIHZhcmlhYmxlcy4gQWZ0ZXIgcHJvY2Vzc2luZyB0aGUgZGF0YSwgd2Ugd2lsbCBoYXZlIGEgZGF0YSB2aXN1YWxpemF0aW9uIHNlY3Rpb24gdGhhdCBzaG93cyB0aGUgZGlzdHJpYnV0aW9uIGFuZCByZWxhdGlvbnNoaXAgYmV0d2VlbiBzb21lIHZhcmlhYmxlcy4gQW5kIHRvIGZpbmQgdGhlIGJlc3QgbW9kZWwsIHdlIGNob29zZSB0byBleHBsb3JlIHRocmVlIG1hY2hpbmUgbGVhcm5pbmcgbW9kZWxzIHdoaWNoIGFyZSBMQVNTTywgUmFuZG9tIGZvcmVzdCBhbmQgU3RhY2tpbmcgbW9kZWxzLiBXZSB1c2UgYSBzdGFja2luZyBtZXRob2QgdG8gY3JlYXRlIHRoZSBzdGFja2luZyBtb2RlbCBieSBjb21iaW5pbmcgdGhyZWUgbW9kZWxzIChMYXNzbywgUmFuZG9tIEZvcmVzdCwgYW5kIEtOTikuIExhc3RseSwgd2Ugd2lsbCBtYWtlIGEgc3RvY2sgcmV0dXJuIHByZWRpY3Rpb24gZm9yIDIwMjEgdXNpbmcgdGhlIHRyYWluZWQgbW9kZWwgd2l0aCB0aGUgbG93ZXN0IFItc3F1YXJlZCBhbmQgUk1TRS4gCgojIyMgTGlzdCBvZiBWYXJpYWJsZXMgCgpGb3IgdGhlIGRhdGFzZXQsIHdlIGluY2x1ZGVzIGZpbmFuY2lhbCBpbmZvcm1hdGlvbiBvbiBjb21wYW5pZXMgaW4gdGhlIFMmUCA1MDAgc3RvY2sgaW5kZXggZnJvbSAxOTk5LTIwMjEuIFRoaXMgaW5mb3JtYXRpb24gd2FzIHNjcmFwZWQgZnJvbSBZYWhvbyBGaW5hbmNlIGluIE5vdmVtYmVyIG9mIDIwMjEsIGFuZCBjb2xsZWN0ZWQgaW4gYSBjc3YgZm9ybWF0IGZvciBkYXRhIGFuYWx5c2lzLiBUaGUgaW5mb3JtYXRpb24gaW5jbHVkZXMgbWV0cmljcyBsaWtlIHNhbGVzLCBlYXJuaW5ncywgY29ncywgc3RvY2sgcHJpY2UsIGFuZCBtYXJrZXQgc2VjdG9yIGFzIHdlbGwgYXMgbWFjcm9lY29ub21pYyBkYXRhIHN1Y2ggYXMgR0RQIG9yIE1vbmV5IFN1cHBseS4gVGhlIGdvYWwgaXMgdG8gYW5hbHl6ZSBhbmQgbW9kZWwgdGhpcyBkYXRhIHRvIGJldHRlciBpbXByb3ZlIHByb2plY3Rpb25zIGZvciBhIGNvbXBhbnnigJlzIGZ1dHVyZSBwcm9maXRhYmlsaXR5LiBUaGUgdmFyaWFibGVzIGluIHRoZSBkYXRhIHNldCBhcmUgZGVzY3JpYmVkIGJlbG93OgoKfCBWYXJpYWJsZSAgICAgICAgICAgIHwgTWVhbmluZyAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgfAp8LS0tLS0tLS0tLS0tLS0tLS0tLS0tfC0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS18CnwgWUVBUiAgICAgICAgICAgICAgICB8IFRoZSBmaW5hbmNpYWwgeWVhciBvZiB0aGUgY29tcGFueSAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHwKfCBDT01QQU5ZICAgICAgICAgICAgIHwgVGhlIGNvbXBhbnnigJlzIHN0b2NrIGFiYnJldmlhdGlvbiBzeW1ib2wgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICB8CnwgTUFSS0VULkNBUCAgICAgICAgICB8IFRoZSB0b3RhbCBtYXJrZXQgY2FwaXRhbGl6YXRpb24gb2YgdGhlIGNvbXBhbnkgKFZvbHVtZSAqIFByaWNlKSAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHwKfCBFQVJOSU5HUyAgICAgICAgICAgIHwgVGhlIGVhcm5pbmdzIGluIGRvbGxhcnMgZm9yIHRoZSBwcmV2aW91cyB5ZWFyIGZvciB0aGUgZ2l2ZW4gY29tcGFueSAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgfAp8IFNBTEVTICAgICAgICAgICAgICAgfCBIb3cgbXVjaCB0aGUgY29tcGFueSBzb2xkIGluIGRvbGxhcnMgbGFzdCB5ZWFyICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICB8CnwgQ0FTSCAgICAgICAgICAgICAgICB8IEhvdyBtdWNoIGNhc2ggdGhlIGNvbXBhbnkgaGFzIGluIGRvbGxhcnMgYXQgdGhlIGVuZCBvZiB0aGUgcHJldmlvdXMgeWVhciAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHwKfCBOYW1lICAgICAgICAgICAgICAgIHwgVGhlIGZ1bGwgbmFtZSBvZiB0aGUgY29tcGFueSAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgfAp8IFNlY3RvciAgICAgICAgICAgICAgfCBUaGUgbmFtZSBvZiB0aGUgc2VjdG9yIHRoYXQgdGhlIGNvbXBhbnkgaXMgYSBwYXJ0IG9mICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICB8CnwgRWFybmluZ3NfbmV4dF95ZWFyICB8IFRoZSBhbW91bnQgb2YgbW9uZXkgaW4gZG9sbGFycyB0aGF0IHRoZSBjb21wYW55IGVhcm5zIGluIHRoZSBmb2xsb3dpbmcgeWVhciAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHwKfCBQUklDRSAgICAgICAgICAgICAgIHwgVGhlIHByaWNlIG9mIHRoZSBzdG9jayB3aGVuIGl0IGlzIGJvdWdodCAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgfCAKfCBTZWxsICAgICAgICAgICAgICAgIHwgVGhlIHByaWNlIG9mIHRoZSBzdG9jayB3aGVuIGl0IGlzIHNvbGQgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgfAp8IFZPTFVNRSAgICAgICAgICAgICAgfCBUaGUgdG90YWwgbnVtYmVyIG9mIHNoYXJlcyB0aGF0IHRoZSBjb21wYW55IGhvbGRzICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICB8CnwgQ09HUyAgICAgICAgICAgICAgICB8IFRoZSB0b3RhbCBhbW91bnQgdGhlIGNvbXBhbnkgcGFpZCBhcyBhIGNvc3QgZGlyZWN0bHkgcmVsYXRlZCB0byB0aGUgc2FsZSBvZiBwcm9kdWN0cyAgICAgICAgICAgICAgICAgICAgICAgIHwgCnwgSU5WRVNUTUVOVCAgICAgICAgICB8IFRoZSB0b3RhbCBhc3NldCBvciBpdGVtIGFjcXVpcmVkIHdpdGggdGhlIGdvYWwgb2YgZ2VuZXJhdGluZyBpbmNvbWUgb3IgYXBwcmVjaWF0aW9uICAgICAgICAgICAgICAgICAgICAgICAgIHwKfCBSRUNJRVZBQkxFICAgICAgICAgIHwgVGhlIGRlYnRzIG93ZWQgdG8gYSBjb21wYW55IGJ5IGl0cyBjdXN0b21lcnMgZm9yIGdvb2RzIHRoYXQgaGF2ZSBiZWVuIGRlbGl2ZXJlZCBvciB1c2VkIGJ1dCBub3QgeWV0IHBhaWQgZm9yfCAKfCBJTlZFTlRPUlkgICAgICAgICAgIHwgSG93IG11Y2ggcmF3IG1hdGVyaWFscyB1c2VkIGluIHByb2R1Y3Rpb24gYXMgd2VsbCBhcyB0aGUgZ29vZHMgcHJvZHVjZWQgdGhhdCBhcmUgYXZhaWxhYmxlIGZvciBzYWxlICAgICAgICAgfCAKfCBERUJUUyAgICAgICAgICAgICAgIHwgSG93IG11Y2ggbW9uZXkgdGhlIGNvbXBhbnkgYm9ycm93IGZyb20gb3RoZXIgcGFydGllcyAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgfCAKfCBDUEFMVFQwMVVTTTY1N05fUEMxIHwgVGhlIHBlcmNlbnRhZ2UgY2hhbmdlIGluIENQSSAobWVhc3VyZSBvZiBpbmZsYXRpb24pICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgfCAKfCBHRFAgICAgICAgICAgICAgICAgIHwgVGhlIG1vbmV0YXJ5IHZhbHVlIG9mIGFsbCBmaW5pc2hlZCBnb29kcyBhbmQgc2VydmljZXMgbWFkZSB3aXRoaW4gYSBjb3VudHJ5ICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgfAp8IEdEUF9QQzEgICAgICAgICAgICAgfCBUaGUgcGVyY2VudGFnZSBjaGFuZ2UgaW4gR0RQICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICB8CnwgVDEwWTJZICAgICAgICAgICAgICB8IFRlbiB5ZWFyIHRyZWFzdXJ5IGJvbmRzIG1pbnVzIHR3byB5ZWFyIHRyZWFzdXJ5IGJvbmRzICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHwKfCBNMVNMICAgICAgICAgICAgICAgIHwgVGhlIHRvdGFsIGN1cnJlbmN5IGFuZCBvdGhlciBsaXF1aWQgaW5zdHJ1bWVudHMgaW4gYSBjb3VudHJ5J3MgZWNvbm9teSAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgfAp8IE0xU0xfUEMxICAgICAgICAgICAgfCBUaGUgcGVyY2VudGFnZSBjaGFuZ2UgaW4gbW9uZXkgc3VwcGx5ICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICB8CnwgRWFybmluZ3NfbmV4dF95ZWFyICB8IEhvdyBtdWNoIHByb2ZpdCB0aGF0IGEgY29tcGFueSBwcm9kdWNlcyBuZXh0IHllYXIgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHwgCnwgUFJPRklUICAgICAgICAgICAgICB8IEhvdyBtdWNoIHRoZSBtb25leSBtYWRlIG9yIGxvc3Qgb24gYW4gaW52ZXN0bWVudCAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHwKCiMjIyBMb2FkaW5nIGRhdGEKCmBgYHtyfQpmaW5hbERBVEFTRVQgPC0gcmVhZF9jc3YoIkZJTkFMREFUQVNFVC5jc3YiKQptYWNyb19kYXRhIDwtIHJlYWRfY3N2KCJjbGVhbl9tYWNybyAtIFNoZWV0MS5jc3YiKQpgYGAKCiMjIERhdGEgUHJlcHJvY2Vzc2luZyAKRm9yIHRoZSBkYXRhIHByZXByb2Nlc3NpbmcsIHdlIGNvbWJpbmVkIG91ciBkYXRhIHNvdXJjZXMgYW5kIGZpbGxlZCBtaXNzaW5nIHZhbHVlcyBmb3Igc29tZSBmdW5kYW1lbnRhbCBmYWN0b3JzIGJ5IHVzaW5nIHRoZSBtZWRpYW4gb2YgdGhhdCB2YWx1ZSBpbiB0aGUgc2VjdG9yIGZvciBhIGNlcnRhaW4geWVhci4gV2UgYWxzbyBjcmVhdGVkIHNlY3RvciBkdW1taWVzIGluIHRoaXMgc3RlcC4gV2UgdGhlbiBkcm9wcGVkIHNvbWUgdmFyaWFibGVzIHRoYXQgYXJlIG5vdCBpbiBvdXIgaW50ZXJlc3RzIGFuZCB0aGUgc3BsaXRlZCB0aGUgdGVzdGluZyBhbmQgdHJhaW5uaW5nIGRhdGEuIApgYGB7cn0KZmluYWxfZGF0YSA8LSBmaW5hbERBVEFTRVQgJT4lIAogICMgbWFrZSBTZWN0b3IgZHVtbXkgdmFyaWFibGVzCiAgZHVtbXlfY29scyhzZWxlY3RfY29sdW1ucyA9ICJTZWN0b3IiKSAlPiUgCiAgIyBtZXJnZSB3aXRoIHVwZGF0ZWQgbWFjcm8gZGF0YQogIHNlbGVjdCgtQ1BBTFRUMDFVU002NTdOX1BDMSwKICAgICAgICAgLUdEUCwKICAgICAgICAgLUdEUF9QQzEsCiAgICAgICAgIC1NMVNMX1BDMSAsCiAgICAgICAgIC1NMVNMLAogICAgICAgICAtUFJJQ0UsCiAgICAgICAgIC1TZWxsLAogICAgICAgICAtQ09NUEFOWSkgJT4lIAogIG1lcmdlKG1hY3JvX2RhdGEpICU+JSAKICAjIGNvbnZlcnQgTWFjcm8gZmFjdG9ycyBmcm9tIGNoYXJhY3RlcnMgdG8gbnVtZXJpYwogIG11dGF0ZShhY3Jvc3MoYygiQ1BBTFRUMDFVU002NTdOX1BDMSIsIkdEUCIsIkdEUF9QQzEiLCJUMTBZMlkiLCJNMVNMX1BDMSIsIk0xU0wiKSwKICAgICAgICAgICAgICAgIGFzLm51bWVyaWMpKSAlPiUgCiAgZ3JvdXBfYnkoWUVBUixTZWN0b3IpICU+JSAKICAjIHJlcGxhY2luZyB0aGUgbWlzc2luZyB2YWx1ZSB3aXRoIG1lZGlhbiBvZCB0aGUgaW5kdXN0cnkgaW4gdGhhdCB5ZWFyCiAgbXV0YXRlKGFjcm9zcyhjKERFQlRTLElOVkVTVE1FTlRTLENBU0gsVk9MVU1FLEVBUk5JTkdTLENPR1MsU0FMRVMsUkVDRUlWQUJMRSxJTlZFTlRPUlkpLH5yZXBsYWNlKC4sLj09MCxtZWRpYW4oLikpKSkgJT4lCiAgIyBkZWxldGUgbGVzcyBpbXBvcnRhbnQgZmFjdG9ycyAtPiBFeGNoYW5nZSBjYW4gcG9zc2liaWx5IGJlIGRlbGV0ZWQKICB1bmdyb3VwKCkgJT4lICAKICBtdXRhdGUoYWNyb3NzKGMoIXdoZXJlKGlzLm51bWVyaWMpLC0iTmFtZSIpLGFzLmZhY3RvcikpICU+JSAKICBzZWxlY3QoLUVhcm5pbmdzX25leHRfeWVhciwtb2JzZXJ2YXRpb25fZGF0ZSkgJT4lIAogIGRyb3BfbmEoKSAKCiMgZmlsdGVyIG91dCB0aGUgZGF0YSBmb3IgMjAyMQpmaW5hbF9kYXRhXzIwMjEgPC0gZmluYWxfZGF0YSAlPiUgCiAgZmlsdGVyKFlFQVIgPT0gMjAyMSkKCmZpbmFsX2RhdGEgPC0gZmluYWxfZGF0YSAlPiUgCiAgZmlsdGVyKFlFQVIgPCAyMDIxKQoKIyBzcGxpdCB0aGUgZGF0YQpzZXQuc2VlZCgzMjcpICNmb3IgcmVwcm9kdWNpYmlsaXR5CgpkYXRhX3NwbGl0IDwtIGluaXRpYWxfc3BsaXQoZmluYWxfZGF0YSwgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgcHJvcCA9IC43NSkKZGF0YV90cmFpbmluZyA8LSB0cmFpbmluZyhkYXRhX3NwbGl0KQpkYXRhX3Rlc3RpbmcgPC0gdGVzdGluZyhkYXRhX3NwbGl0KQoKIyBxdWljayBsb29rIG9mIHRoZSBkYXRhIApmaW5hbF9kYXRhICU+JSAKICBoZWFkKDUpCmBgYAoKIyMgRGF0YSBWaXN1YWxpemF0aW9uCldlIGZpcnN0IGV4cGxvcmVkIGRpc3RyaWJ1dGlvbnMgb2Ygb3VyIHByZWRpY3RvcnMgYW5kIG91dGNvbWUuIEFzIHRoZSBmb2xsb3dpbmcgZ3JhcGggc3VnZ2VzdGVkLCBhbGwgdGhlIG51bWVyaWMgdmFyaWFibGVzIGFyZSBzZXZlcmVseSByaWdodC1za2V3ZWQgd2l0aCBzb21lIG91dGxpZXJzLiBJbiBvdXIgZGF0YSwgdGhvdWdoIHRoZSBudW1iZXJzIG9mIGZpcm1zIGluIGVhY2ggc2VjdG9yIGFyZSB1bmJhbGFuY2VkLCB3ZSBnb3QgYSBkZWNlbnQgYW1vdW50IG9mIGRhdGEgZm9yIGVhY2ggc2VjdG9yLiAgCmBgYHtyfQoKICBmaW5hbF9kYXRhICU+JSAKICBzZWxlY3Qod2hlcmUoaXMubnVtZXJpYykpICU+JSAKICBzZWxlY3QoLXN0YXJ0c193aXRoKCJTZWN0b3JfIikpICU+JSAKICBwaXZvdF9sb25nZXIoY29scyA9IGV2ZXJ5dGhpbmcoKSwKICAgICAgICAgICAgICAgbmFtZXNfdG8gPSAidmFyaWFibGUiLCAKICAgICAgICAgICAgICAgdmFsdWVzX3RvID0gInZhbHVlIikgJT4lIAogIGdncGxvdChhZXMoeCA9IHZhbHVlKSkgKwogIGdlb21faGlzdG9ncmFtKGJpbnMgPSAzMCkgKwogIGZhY2V0X3dyYXAodmFycyh2YXJpYWJsZSksIAogICAgICAgICAgICAgc2NhbGVzID0gImZyZWUiLAogICAgICAgICAgICAgbnJvdyA9IDQsCiAgICAgICAgICAgICBuY29sPTUpCgpgYGAKYGBge3J9CmZpbmFsX2RhdGEgJT4lIAogIHNlbGVjdCh3aGVyZShpcy5mYWN0b3IpKSAlPiUgCiAgcGl2b3RfbG9uZ2VyKGNvbHMgPSBldmVyeXRoaW5nKCksCiAgICAgICAgICAgICAgIG5hbWVzX3RvID0gInZhcmlhYmxlIiwgCiAgICAgICAgICAgICAgIHZhbHVlc190byA9ICJ2YWx1ZSIpICU+JSAKICBnZ3Bsb3QoYWVzKHggPSB2YWx1ZSkpICsKICBnZW9tX2JhcigpICsKICBmYWNldF93cmFwKHZhcnModmFyaWFibGUpLCAKICAgICAgICAgICAgIHNjYWxlcyA9ICJmcmVlIiwgCiAgICAgICAgICAgICBucm93ID0gNCkgKyAKICB0aGVtZShheGlzLnRleHQueCA9IGVsZW1lbnRfdGV4dChhbmdsZSA9IDQ1KSkKCmBgYAoKICAKVGhlbiwgd2UgY29udGludWVkIHRvIGV4cGxvcmUgdGhlIHJlbGF0aW9uc2hpcCBiZXR3ZWVuIHJlZ3Jlc3NvcnMgYW5kIHRoZSBvdXRjb21lLiBCeSBvYnNlcnZhdGlvbiwgd2UgY2FuJ3Qgc2VlIGEgc3Ryb25nIGNvcnJlbGF0aW9uIGJldHdlZW4gZnVuZGVtZW50YWwgZmFjdG9ycywgc3VjaCBhcyBpbnZlc3RtZW50cyBhbmQgZGVidHMsIGFuZCBzdG9jayByZXR1cm4uIEl0IG1heSBpbmRpY2F0ZSB0aGF0IGxpbmVhciBtb2RlbCBpcyBub3QgYW4gaWRlYWwgbW9kZWwgaW4gdGhpcyBjYXNlLiAKCmBgYHtyfQpmaW5hbF9kYXRhICU+JSAKICBnZ3Bsb3QoYWVzKHggPSBJTlZFU1RNRU5UUywgeSA9IFBST0ZJVCxjb2xvciA9IFlFQVIpKSArIAogIGdlb21fcG9pbnQoYWxwaGEgPSAwLjUpKwogIGdlb21fc21vb3RoKHNlID0gRkFMU0UpICsgCiAgbGFicyh0aXRsZSA9ICJSZWxhdGlvbnNoaXAgYmV0d2VlbiBpbnZlc3RtZW50cyBhbmQgc3RvY2sgcmV0dXJuIikKYGBgCgpgYGB7cn0KZmluYWxfZGF0YSAlPiUgCiAgZ2dwbG90KGFlcyh4ID0gREVCVFMsIHkgPSBQUk9GSVQsY29sb3IgPSBZRUFSKSkgKyAKICBnZW9tX3BvaW50KGFscGhhID0gMC41KSsKICBnZW9tX3Ntb290aChzZSA9IEZBTFNFKSArIAogIGxhYnModGl0bGUgPSAiUmVsYXRpb25zaGlwIGJldHdlZW4gZGVidHMgYW5kIHN0b2NrIHJldHVybiIpCmBgYAoKV2l0aCB0aGlzIGFuYW1pYXRpb24sIHdlIGNvdWxkIHNlZSB0aGF0IHRoZSByZXR1cm5zIG9mIHN0b2NrcyBmb2xsb3cgYSBjeWNsZSwgd2hpY2ggbWF5IGJlIGluZmx1ZW5jZWQgYnkgbWFjcm9lY29ub21pYyBjb25kaXRpb24uIFRoZSByZXR1cm4gaW4gMTk5OSBhbmQgMjAxOSBzZWVtcyB0byBiZSB0aGUgaGlnaGVzdCBmb3IgYWxsIGluZHVzdHJpZXMuIEFsc28sIHdlIG5vdGljZWQgdGhhdCBzb21lIGluZHVzdHJpZXMgdmFyeSBhIGxvdCB5ZWFyIHRvIHllYXIsIHN1Y2ggYXMgSVQgYW5kIGhlYWx0aGNhcmUgaW5kdXN0cnkuIAoKYGBge3IgZXZhbCA9IEZBTFNFfQpzZWN0b3JfcmV0dXJuX2FuPC1maW5hbF9kYXRhICU+JQogIGdncGxvdChhZXMoeCA9IFBST0ZJVCwgeSA9IFNlY3RvcikpICsKICBnZW9tX2JveHBsb3QoYWVzKGNvbG9yID0gU2VjdG9yKSwKICAgICAgICAgICAgIGFscGhhID0gLjgsCiAgICAgICAgICAgICBzaXplID0gMSkgKyAKICBsYWJzKHRpdGxlID0gIlNwcmVhZCBvZiBzdG9jayByZXR1cm4gaW4gZGlmZmVyZW50IHNlY3RvciIsCiAgICAgICBzdWJ0aXRsZSA9ICJZRUFSOiB7Y2xvc2VzdF9zdGF0ZX0iLAogICAgICAgY29sb3IgPSAiIikgKyAKICB0cmFuc2l0aW9uX3N0YXRlcyhZRUFSKQogIAphbmltYXRlKHNlY3Rvcl9yZXR1cm5fYW4sIGR1cmF0aW9uID0gMjUpCmFuaW1fc2F2ZSgic2VjdG9yX3JldHVybi5naWYiKQpgYGAKCmBgYHtyfQprbml0cjo6aW5jbHVkZV9ncmFwaGljcygic2VjdG9yX3JldHVybi5naWYiKQpgYGAKCldlIGZ1cnRoZXIgZXhwbG9yZWQgdGhlIHJlbGF0aW9uc2hpcCBiZXR3ZWVuIHRoZSBpbmR1c3RyeSBhbmQgdGhlIHBvdGVudGlhbCBtYWNyb2Vjb25vbWljIGluZmx1ZW5jZXIgdXNpbmcgZ3JhcGhzIGJlbG93LiBUaG91Z2ggdGhlIHJlbGF0aW9uc2hpcCBkb2Vzbid0IHNlZW0gdG8gYmUgbGluZWFyLCB3ZSBkbyBzZWUgaG93IG1hY3JvZWNvbm9taWMgY29uZGl0aW9uIGFmZmVjdCBlYWNoIGluZHVzdHJ5IGRpZmZlcmVudGx5IGFuZCB3aWxsIGFjY291bnQgZm9yIHRoYXQgaW4gb3VyIG1vZGVsLgoKYGBge3J9CmZpbmFsX2RhdGEgJT4lIAogIGdyb3VwX2J5KFlFQVIsU2VjdG9yKSAlPiUgCiAgbXV0YXRlKHByb2ZpdF9wb3IgPSBQUk9GSVQqYE1BUktFVCBDQVBgLyhzdW0oYE1BUktFVCBDQVBgLG5hLnJtID0gVFJVRSkpKSAlPiUgCiAgc3VtbWFyaXNlKFNlY3Rvcl9wcm9maXQgPSBzdW0ocHJvZml0X3BvciksCiAgICAgICAgICAgIEdEUF9QQzEgPSBtZWFuKEdEUF9QQzEpLAogICAgICAgICAgICBDUEFMVFQwMVVTTTY1N05fUEMxID0gbWVhbihDUEFMVFQwMVVTTTY1N05fUEMxKSwKICAgICAgICAgICAgU2VjdG9yID0gU2VjdG9yWzFdKSAlPiUgCiAgZ2dwbG90KGFlcyh4ID0gQ1BBTFRUMDFVU002NTdOX1BDMSx5ID0gU2VjdG9yX3Byb2ZpdCxjb2xvciA9IFNlY3RvcikpKwogIGdlb21fc21vb3RoKHNlID0gRkFMU0UpICsgCiAgbGFicyh0aXRsZSA9ICJSZWxhdGlvbnNoaXAgYmV0d2VlbiBpbmZsYXRpb24gYW5kIGF2ZXJhZ2UgcmV0dXJuIG9mIGEgaW5kdXN0cnkiLAogICAgICAgeCA9ICJQZXJjZW50IENoYW5nZSBpbiBDUEkiLAogICAgICAgeSA9ICJBdmVyYWdlIFJldHVybiBvZiBhIHNlY3RvciIpCiAgCmBgYAoKYGBge3J9CmZpbmFsX2RhdGEgJT4lIAogIGdyb3VwX2J5KFlFQVIsU2VjdG9yKSAlPiUgCiAgbXV0YXRlKHByb2ZpdF9wb3IgPSBQUk9GSVQqYE1BUktFVCBDQVBgLyhzdW0oYE1BUktFVCBDQVBgLG5hLnJtID0gVFJVRSkpKSAlPiUgCiAgc3VtbWFyaXNlKFNlY3Rvcl9wcm9maXQgPSBzdW0ocHJvZml0X3BvciksCiAgICAgICAgICAgIEdEUF9QQzEgPSBtZWFuKEdEUF9QQzEpLAogICAgICAgICAgICBDUEFMVFQwMVVTTTY1N05fUEMxID0gbWVhbihDUEFMVFQwMVVTTTY1N05fUEMxKSwKICAgICAgICAgICAgU2VjdG9yID0gU2VjdG9yWzFdKSAlPiUgCiAgZ2dwbG90KGFlcyh4ID0gR0RQX1BDMSx5ID0gU2VjdG9yX3Byb2ZpdCxjb2xvciA9IFNlY3RvcikpKwogIGdlb21fc21vb3RoKHNlID0gRkFMU0UpICsgCiAgbGFicyh0aXRsZSA9ICJSZWxhdGlvbnNoaXAgYmV0d2VlbiBHRFAgIGFuZCBhdmVyYWdlIHJldHVybiBvZiBhIGluZHVzdHJ5IiwKICAgICAgIHggPSAiUGVyY2VudCBDaGFuZ2UgaW4gR0RQIiwKICAgICAgIHkgPSAiQXZlcmFnZSBSZXR1cm4gb2YgYSBzZWN0b3IiKQpgYGAKCgojIyBMYXNzbyBNb2RlbCAKIyMjIEJ1aWxkaW5nIFJlY2lwZQoKSW4gdGhlIGxhc3NvIG1vZGVsLCB0byBhY2NvdW50IGZvciB0aGUgZmFjdCB0aGUgbWFjcm9lY29ub21pYyBjb25kaXRpb24gYWZmZWN0cyBlYWNoIGluZHVzdHJ5IGRpZmZlcmVudGx5LCB3ZSBhbHNvIGluY2x1ZGVkIHRoZSBpbnRlcmFjdGlvbiB0ZXJtIGJldHdlZW4gR0RQIGFuZCBzZWN0b3IgZHVtbWllcy4gCgpgYGB7cn0KcmV0dXJuX3JlY2lwZSA8LSByZWNpcGUoUFJPRklUIH4gLiwgI3Nob3J0LWN1dCwgLiA9IGFsbCBvdGhlciB2YXJzCiAgICAgICAgICAgICAgICAgICAgICAgZGF0YSA9IGRhdGFfdHJhaW5pbmcpICU+JSAKICAjIGZpbHRlciB0byBvbmx5IGhhdmUgZGF0YSBhZnRlciAyMDIwCiAgc3RlcF9maWx0ZXIoWUVBUjwyMDIxKSAlPiUgCiAgc3RlcF9ybShOYW1lLFNlY3RvcixZRUFSKSAlPiUgCiAgI3N0ZXBfcm0oR0RQLE0xU0wsTmFtZSxTZWN0b3IsWUVBUikgJT4lIAogICMgYWRkIFBFIAogIHN0ZXBfbXV0YXRlKFBFID0gYE1BUktFVCBDQVBgL0VBUk5JTkdTKSAlPiUKICAjIE5vcm1hbGl6ZSBhbGwgdmFyaWFibGVzIGV4Y2VwdCBmb3IgR0RQCiAgc3RlcF9ub3JtYWxpemUoYWxsX3ByZWRpY3RvcnMoKSwgCiAgICAgICAgICAgICAgICAgLWFsbF9ub21pbmFsKCksCiAgICAgICAgICAgICAgICAgLXN0YXJ0c193aXRoKCJTZWN0b3JfIikpICU+JSAKICAjIENyZWF0ZSBpbnRlcmFjdGlvbiB0ZXJtcwogIHN0ZXBfaW50ZXJhY3QodGVybXMgPSB+YyhHRFBfUEMxKTpzdGFydHNfd2l0aCgiU2VjdG9yXyIpKSAKIyBzaG93IHRoZSBkYXRhIGluIHJlY2lwZQpyZXR1cm5fcmVjaXBlICU+JSAKICBwcmVwKGRhdGFfdHJhaW5pbmcpICU+JQogICMgdXNpbmcgYmFrZShuZXdfZGF0YSA9IE5VTEwpIGdpdmVzIHNhbWUgcmVzdWx0IGFzIGp1aWNlKCkKICAjIGJha2UobmV3X2RhdGEgPSBOVUxMKQogIGp1aWNlKCkgCmBgYAoKIyMjIFNlbGVjdCB0dW5pbmcgcGFyYW1ldGVyCgpgYGB7cn0KcmV0dXJuX2xpbmVhcl9tb2QgPC0gCiAgIyBEZWZpbmUgYSBsYXNzbyBtb2RlbCAKICAjIEkgYmVsaWV2ZSBkZWZhdWx0IGlzIG1peHR1cmUgPSAxIHNvIHByb2JhYmx5IGRvbid0IG5lZWQgCiAgbGluZWFyX3JlZyhtaXh0dXJlID0gMSkgJT4lIAogICMgU2V0IHRoZSBlbmdpbmUgdG8gImdsbW5ldCIgCiAgc2V0X2VuZ2luZSgiZ2xtbmV0IikgJT4lIAogICMgVGhlIHBhcmFtZXRlcnMgd2Ugd2lsbCB0dW5lLgogIHNldF9hcmdzKHBlbmFsdHkgPSB0dW5lKCkpICU+JSAKICAjIFVzZSAicmVncmVzc2lvbiIKICBzZXRfbW9kZSgicmVncmVzc2lvbiIpCgpzZXQuc2VlZCg0NTYpCnJldHVybl9sbV93ZiA8LSAKICAjIFNldCB1cCB0aGUgd29ya2Zsb3cKICB3b3JrZmxvdygpICU+JSAKICAjIEFkZCB0aGUgcmVjaXBlCiAgYWRkX3JlY2lwZShyZXR1cm5fcmVjaXBlKSAlPiUgCiAgIyBBZGQgdGhlIG1vZGVsaW5nCiAgYWRkX21vZGVsKHJldHVybl9saW5lYXJfbW9kKQoKcGVuYWx0eV9ncmlkIDwtIGdyaWRfcmVndWxhcihwZW5hbHR5KCksCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgbGV2ZWxzID0gMTApCgpyZXR1cm5fY3YgPC0gdmZvbGRfY3YoZGF0YV90cmFpbmluZywgdiA9IDUpCgpyZXR1cm5fbG1fdHVuZSA8LSAKICByZXR1cm5fbG1fd2YgJT4lIAogIHR1bmVfZ3JpZCgKICAgIHJlc2FtcGxlcyA9IHJldHVybl9jdiwKICAgIGdyaWQgPSBwZW5hbHR5X2dyaWQKICAgICkKCmJlc3RfcGFyYW08LXJldHVybl9sbV90dW5lICU+JSAKICBzZWxlY3RfYmVzdChtZXRyaWMgPSAicm1zZSIpCgpyZXR1cm5fbGFzc29fZmluYWxfd2YgPC0gcmV0dXJuX2xtX3dmICU+JSAKICBmaW5hbGl6ZV93b3JrZmxvdyhiZXN0X3BhcmFtKQoKcmV0dXJuX2xhc3NvX2ZpbmFsX21vZCA8LSByZXR1cm5fbGFzc29fZmluYWxfd2YgJT4lIAogIGZpdChkYXRhID0gZGF0YV90cmFpbmluZykKCiMgdmlzdWxpemF0aW9uIGZvciBiZXN0IHBhcmFtCnNldC5zZWVkKDQ1NikKcmV0dXJuX2xtX3R1bmUgJT4lIAogIGNvbGxlY3RfbWV0cmljcygpICU+JSAKICBmaWx0ZXIoLm1ldHJpYyA9PSAicm1zZSIpICU+JSAKICBnZ3Bsb3QoYWVzKHggPSBwZW5hbHR5LCB5ID0gbWVhbikpICsKICBnZW9tX3BvaW50KCkgKwogIGdlb21fbGluZSgpICsKICBzY2FsZV94X2xvZzEwKAogICBicmVha3MgPSBzY2FsZXM6OnRyYW5zX2JyZWFrcygibG9nMTAiLCBmdW5jdGlvbih4KSAxMF54KSwKICAgbGFiZWxzID0gc2NhbGVzOjp0cmFuc19mb3JtYXQoImxvZzEwIixzY2FsZXM6Om1hdGhfZm9ybWF0KDEwXi54KSkpICsKICBsYWJzKHggPSAicGVuYWx0eSIsIHkgPSAicm1zZSIpCmBgYAoKCgojIyMgTGFzc28gcmVzdWx0cwoKVGhlIHRhYmxlIGJlbG93IHNob3dzIHRoZSBlc3RpbWF0ZSBvZiBlYWNoIHByZWRpY3RvciBpbiB0aGUgTGFzc28gTW9kZWwuICBXZSBjYW4gc2VlIHRoYXQgaW5kaWNhdG9ycyBpbiBtYWNyb2Vjb25vbWljcyBhcmUgcmVsYXRpdmVseSBpbXBvcnRhbnQgdG8gcHJlZGljdCB0aGUgc3RvY2sgcmV0dXJuLiBUaGUgY2hhbmdlIGluIGluZmxhdGlvbiBhbmQgR0RQIGFsbCByZW1haW4gc2lnbmlmaWNhbnQgYWZ0ZXIgc2hyaW5raW5nLiBUaGUgaW50ZXJhY3Rpb24gdGVybXMgYmV0d2VlbiBHRFAgYW5kIGluZHVzdHJpZXMgYWxzbyBzaG93ZWQgaW1wb3J0YW5jZSwgYWNjb3VudGluZyBmb3IgdGhlIGZhY3QgdGhhdCB0aGUgbWFjcm9lY29ub21pYyBjb25kaXRpb24gYWZmZWN0cyBlYWNoIGluZHVzdHJ5IGRpZmZlcmVudGx5OiBHRFAgc2VlbXMgdG8gYWZmZWN0IHRoZSBzdG9jayByZXR1cm4gaW4gQ29tbXVuaWNhdGlvbiBTZXJ2aWNlcywgRW5lcmd5LCBhbmQgQ29uc3VtZXIgRGlzY3JldGlvbmFyeSBzZWN0b3JzIGxlc3MuIAoKYGBge3J9CnJldHVybl9sYXNzb19maW5hbF9tb2QgJT4lIAogIHB1bGxfd29ya2Zsb3dfZml0KCkgJT4lIAogIHRpZHkoKSAlPiUgCiAgYXJyYW5nZShkZXNjKGVzdGltYXRlKSkgJT4lIAogIGtibCgpICU+JSAKICBrYWJsZV9zdHlsaW5nKGJvb3RzdHJhcF9vcHRpb25zID0gYygic3RyaXBlZCIsICJib3JkZXJlZCIsICJob3ZlciIsICJjb25kZW5zZWQiKSkgJT4lIAogIHNjcm9sbF9ib3god2lkdGggPSAiMTAwJSIsIGhlaWdodCA9ICI1MDBweCIpCmBgYAoKIyMjIE1vZGVsIEV2YWx1YXRpb24KCiMjIyMgUHJlZGljaXRvbiBwcmVjaXNpb24gCgpUaGUgTGFzc28gbW9kZWwgZG9lcyBub3QgcGVyZm9ybSB3ZWxsLiBJdCBvbmx5IGV4cGxhaW5zIDIwJSBvZiB0aGUgdmFyYWluY2UgaW4gc3RvY2sgcmV0dXJuIGFuZCBpdCBhbHNvIGhhcyBhIHJlbGF0aXZlbHkgaGlnaCBybXNlIG9mIGFyb3VuZCA0Ni4gCgpgYGB7cn0KcmV0dXJuX2xtX3R1bmUgJT4lCiAgc2VsZWN0KGlkLCAubWV0cmljcykgJT4lCiAgdW5uZXN0KC5tZXRyaWNzKSAlPiUKICBmaWx0ZXIoLm1ldHJpYyA9PSAicnNxIikgJT4lIAogIGZpbHRlciguY29uZmlnID09ICJQcmVwcm9jZXNzb3IxX01vZGVsMDgiKSAlPiUgCiAgc3VtbWFyaXNlKG1lYW5fcnNxID0gbWVhbiguZXN0aW1hdGUpKQoKcmV0dXJuX2xtX3R1bmUgJT4lCiAgc2VsZWN0KGlkLCAubWV0cmljcykgJT4lCiAgdW5uZXN0KC5tZXRyaWNzKSAlPiUKICBmaWx0ZXIoLm1ldHJpYyA9PSAicm1zZSIpICU+JSAKICBmaWx0ZXIoLmNvbmZpZyA9PSAiUHJlcHJvY2Vzc29yMV9Nb2RlbDA4IikgJT4lIAogIHN1bW1hcmlzZShtZWFuX3Jtc2UgPSBtZWFuKC5lc3RpbWF0ZSkpCmBgYAoKYGBge3IgZXZhbCA9IEZBTFNFfQpzZXQuc2VlZCg0NTYpCnByZWRpY3Rpb24gPC0gcHJlZGljdCgKICByZXR1cm5fbGFzc29fZmluYWxfbW9kLAogIG5ld19kYXRhID0gZGF0YV90cmFpbmluZykKCnRyYWluaW5nX3ByZWQ8LWRhdGFfdHJhaW5pbmcgJT4lIAogIG11dGF0ZSgucHJlZCA9IHByZWRpY3Rpb24kLnByZWQpCiAgCiAgCnRyYWluaW5nX3ByZWQgJT4lIAogIGdncGxvdChhZXMoeCA9IFBST0ZJVCwgCiAgICAgICAgICAgICB5ID0gLnByZWQsCiAgICAgICAgICAgICBjb2xvciA9IFlFQVIpKSArCiAgZ2VvbV9wb2ludChhbHBoYSA9IC41LCAKICAgICAgICAgICAgIHNpemUgPSAuNSkgKwogIGdlb21fc21vb3RoKHNlID0gRkFMU0UpICsKICBnZW9tX2FibGluZShzbG9wZSA9IDEsIAogICAgICAgICAgICAgIGludGVyY2VwdCA9IDAsIAogICAgICAgICAgICAgIGNvbG9yID0gImRhcmtyZWQiKSArCiAgZ2VvbV90ZXh0KGFlcyhsYWJlbCA9IE5hbWUpLGxhYmVsLnNpemUgPSAwLjE1LGRhdGEgPSB0cmFpbmluZ19wcmVkICU+JSBmaWx0ZXIoUFJPRklUPjQwMCkgKSsKICBsYWJzKHggPSAiQWN0dWFsIFJldHVybiIsIAogICAgICAgeSA9ICJQcmVkaWN0ZWQgUmV0dXJuIikgKwogIHNjYWxlX2NvbG9yX3ZpcmlkaXNfYigpCmBgYAoKIyMjIyBPdmVyZml0dGluZwoKQmVsb3cgc2hvd3MgdGhlIHItc3F1YXJlIGFuZCBybXNlIG9uIHRlc3RpbmcgZGF0YS4gVGhleSBhcmUgdmVyeSBzaW1pbGFyIHRvIHRob3NlIG9uIHRyYWlubmluZyBkYXRhIHNvIHdlIHdvdWxkIHNhZmVseSBjb25jbHVkZSB0aGF0IG91ciBtb2RlbCBkaWQgbm90IG92ZXJmaXQuIApgYGB7cn0KcmV0dXJuX2xhc3NvX3Rlc3QgPC0gcmV0dXJuX2xhc3NvX2ZpbmFsX3dmICU+JSAKICBsYXN0X2ZpdChkYXRhX3NwbGl0KQoKIyBNZXRyaWNzIGZvciBtb2RlbCBhcHBsaWVkIHRvIHRlc3QgZGF0YQpyZXR1cm5fbGFzc29fdGVzdCAlPiUgCiAgY29sbGVjdF9tZXRyaWNzKCkKYGBgCgpUaGUgZm9sbG93aW5nIGdyYXBocyBzaG93IHRoZSBwZXJmb3JtYW5jZSBvZiB0aGUgbW9kZWwgb24gdGhlIHRlc3RpbmcgZGF0YTogCgpgYGB7cn0KdGVzdF9wcmVkaWN0aW9uIDwtIHByZWRpY3QoCiAgcmV0dXJuX2xhc3NvX2ZpbmFsX21vZCwKICBuZXdfZGF0YSA9IGRhdGFfdGVzdGluZykKCnRlc3RpbmdfbGFzc29fcHJlZDwtZGF0YV90ZXN0aW5nICU+JSAKICBtdXRhdGUoLnByZWQgPSB0ZXN0X3ByZWRpY3Rpb24kLnByZWQpCiAgCiAgCnRlc3RpbmdfbGFzc29fcHJlZCAlPiUgCiAgZ2dwbG90KGFlcyh4ID0gUFJPRklULCAKICAgICAgICAgICAgIHkgPSAucHJlZCwKICAgICAgICAgICAgIGNvbG9yID0gWUVBUikpICsKICBnZW9tX3BvaW50KGFscGhhID0gLjUsIAogICAgICAgICAgICAgc2l6ZSA9IC41KSArCiAgZ2VvbV9zbW9vdGgoc2UgPSBGQUxTRSkgKwogIGdlb21fYWJsaW5lKHNsb3BlID0gMSwgCiAgICAgICAgICAgICAgaW50ZXJjZXB0ID0gMCwgCiAgICAgICAgICAgICAgY29sb3IgPSAiZGFya3JlZCIpICsKICBnZW9tX3RleHQoYWVzKGxhYmVsID0gTmFtZSksbGFiZWwuc2l6ZSA9IDAuMTUsZGF0YSA9IHRlc3RpbmdfbGFzc29fcHJlZCAlPiUgZmlsdGVyKFBST0ZJVD4zMDApICkrCiAgbGFicyh4ID0gIkFjdHVhbCBSZXR1cm4iLCAKICAgICAgIHkgPSAiUHJlZGljdGVkIFJldHVybiIpICsgCiAgc2NhbGVfY29sb3JfdmlyaWRpc19iKCkKYGBgCgojIyMgSW50ZXJwcmV0YWJsZSBNYWNoaW5lIExlYXJuaW5nIAoKQWdhaW4sIHRoZSBib3hwbG90IGFuZCB0aGUgaGlzdG9ncmFtIG9mIHJlc2lkdWFscywgdGhlIG1vZGVsIGRvZXMgbm90IHByZWRpY3QgdGhlIHJldHVybiB3ZWxsIGFuZCB0aGlzIG1heSBiZWNhdXNlIHNvbWUgZXh0cmVtZSB2YWx1ZXMuIApgYGB7cn0KbGFzc29fZXhwbGFpbiA8LSAKICBleHBsYWluX3RpZHltb2RlbHMoCiAgICBtb2RlbCA9IHJldHVybl9sYXNzb19maW5hbF9tb2QsCiAgICBkYXRhID0gZGF0YV90cmFpbmluZyAlPiUgc2VsZWN0KC1QUk9GSVQpLCAKICAgIHkgPSBkYXRhX3RyYWluaW5nICU+JSAgcHVsbChQUk9GSVQpLAogICAgbGFiZWwgPSAibGFzc28iCiAgKQpgYGAKCmBgYHtyfQpsYXNzb19tb2RfcGVyZiA8LSBtb2RlbF9wZXJmb3JtYW5jZShsYXNzb19leHBsYWluKQpoaXN0X3Bsb3QgPC0gCiAgcGxvdChsYXNzb19tb2RfcGVyZiwKICAgICAgIGdlb20gPSAiaGlzdG9ncmFtIikKYm94X3Bsb3QgPC0KICBwbG90KGxhc3NvX21vZF9wZXJmLAogICAgICAgZ2VvbSA9ICJib3hwbG90IikKCmhpc3RfcGxvdApib3hfcGxvdAoKYGBgCgpCZWxvdyBzaG93cyB0aGUgZmVhdHVyZSBpbXBvcnRhbmNlIHBsb3RzIGdlbmVyYXRlZCBieSB0d28gZGlmZmVyZW50IG1ldGhvZHMuIFdlIGNvdWxkIHNlZSB0aGF0IG1hY3JvZWNvbm9taWMgaW5kaWNhdG9ycyByZW1haW5lZCBpbXBvcnRhbmNlIGluIGJvdGggb2YgdGhlIHBsb3RzIGJ1dCBtYXJrZXQgY2FwIHNlZW1zIHRvIGJlIGFuIGNydWNpYWwgZmFjdG9yIHdoZW4gd2UgdXNlIHBlcm11dGF0aW9uIG1ldGhvZC4KCmBgYHtyfQpzZXQuc2VlZCgxMCkgI3NpbmNlIHdlIGFyZSBzYW1wbGluZyAmIHBlcm11dGluZywgd2Ugc2V0IGEgc2VlZCBzbyB3ZSBjYW4gcmVwbGljYXRlIHRoZSByZXN1bHRzCmxhc3NvX3Zhcl9pbXAgPC0gCiAgbW9kZWxfcGFydHMoCiAgICBsYXNzb19leHBsYWluCiAgICApCgpwbG90KGxhc3NvX3Zhcl9pbXAsIHNob3dfYm94cGxvdHMgPSBUUlVFKQpgYGAKYGBge3J9CnJldHVybl9sYXNzb19maW5hbF9tb2QgJT4lIAogIHB1bGxfd29ya2Zsb3dfZml0KCkgJT4lIAogIHZpcCgpCmBgYAoKIyMgUmFuZG9tIEZvcmVzdCBNb2RlbCAKCiMjIyBCdWlsZGluZyBSYW5kb20gRm9yZXN0IG1vZGVsIAoKVG8gYnVpbGQgdGhlIHJhbmRvbSBmb3Jlc3QgbW9kZWwsIHdlIHNldCB1cCByZWNpcGUgdXNpbmcgb3VyIHRyYWluaW5nIGRhdGEgKGBkYXRhX3RyYWluaW5nYCksIGRlZmluZSBtb2RlbCB3aXRoIGBtdHJ5ID0gNmAsIGBtaW5fbiA9IDEwYCBhbmQgYG51bWJlcnMgb2YgdHJlZSA9IDIwMGAsIGNyZWF0ZSByYW5nZXIgd29ya2Zsb3csIGFuZCB0aGVuIHdlIGNhbiBmaXQgdGhlIG1vZGVsLiAKCmBgYHtyfQojIHNldCB1cCByZWNpcGUgCnJhbmdlcl9yZWNpcGUgPC0KICByZWNpcGUoUFJPRklUIH4gLiwgI3Nob3J0LWN1dCwgLiA9IGFsbCBvdGhlciB2YXJzCiAgICAgICAgICAgICAgICAgICAgICAgZGF0YSA9IGRhdGFfdHJhaW5pbmcpICU+JQogIHN0ZXBfZmlsdGVyKFlFQVI8MjAyMSkgJT4lIAogICMgcmVtb3ZlIHRoZSB1bndhbnRlZCB2YXJpYWJsZXMKICBzdGVwX3JtKFlFQVIsTmFtZSxHRFAsTTFTTCxTZWN0b3IpICU+JSAKICAjIGFkZCBQRSAKICBzdGVwX211dGF0ZShQRSA9IGBNQVJLRVQgQ0FQYC9FQVJOSU5HUykKCiNkZWZpbmUgbW9kZWwKcmFuZ2VyX3NwZWMgPC0gCiAgcmFuZF9mb3Jlc3QobXRyeSA9IDYsIAogICAgICAgICAgICAgIG1pbl9uID0gMTAsIAogICAgICAgICAgICAgIHRyZWVzID0gMjAwKSAlPiUgCiAgc2V0X21vZGUoInJlZ3Jlc3Npb24iKSAlPiUgCiAgc2V0X2VuZ2luZSgicmFuZ2VyIikKCiNjcmVhdGUgd29ya2Zsb3cKcmFuZ2VyX3dvcmtmbG93IDwtIAogIHdvcmtmbG93KCkgJT4lIAogIGFkZF9yZWNpcGUocmFuZ2VyX3JlY2lwZSkgJT4lIAogIGFkZF9tb2RlbChyYW5nZXJfc3BlYykKCiNmaXQgdGhlIG1vZGVsCnNldC5zZWVkKDcxMikgIyBmb3IgcmVwcm9kdWNpYmlsaXR5IC0gcmFuZG9tIHNhbXBsaW5nIGluIHJhbmRvbSBmb3Jlc3QgY2hvb3NpbmcgbnVtYmVyIG9mIHZhcmlhYmxlcwpyYW5nZXJfZml0IDwtIHJhbmdlcl93b3JrZmxvdyAlPiUgCiAgZml0KGRhdGFfdHJhaW5pbmcpCmBgYAoKIyMjIFJhbmRvbSBGb3Jlc3QgcmVzdWx0cwoKVGhlIHRhYmxlIGJlbG93IHNob3dzIHRoZSBPT0IgZXJyb3IgKE1TRSksIE9PQiBSTVNFIChSb290IG1lYW4gc3F1YXJlIGVycm9yKSwgYW5kIFIgc3F1YXJlZCBvZiB0aGUgc3RvY2sgcmV0dXJuIHByZWRpY3Rpb25zIHVzaW5nIHRoZSByYW5kb20gZm9yZXN0IG1vZGVsIGFib3ZlLgoKT09CIGVycm9yOiAKYGBge3J9CnJhbmdlcl9maXQkZml0JGZpdCRmaXQkcHJlZGljdGlvbi5lcnJvcgpgYGAKCk9PQiBSTVNFOiAKYGBge3J9CnNxcnQocmFuZ2VyX2ZpdCRmaXQkZml0JGZpdCRwcmVkaWN0aW9uLmVycm9yKQpgYGAKClIgU3F1YXJlZDogCmBgYHtyfQpyYW5nZXJfZml0JGZpdCRmaXQkZml0JHIuc3F1YXJlZApgYGAKCiMjIyBNb2RlbCBFdmFsdWF0aW9uCiMjIyMgUHJlZGljaXRvbiBwcmVjaXNpb24gKFRyYW5pbmcgZGF0YSkKCkV2ZW4gdGhvdWdoIHRoZSBtZWFuIHJtc2UgZnJvbSB0aGUgcmFuZG9tIGZvcmVzdCAoMzkuMzA3ODcpIHNlZW1zIHRvIGJlIGZhaXJseSBoaWdoIGJ1dCBpdCBpcyBzdGlsbCBsb3dlciB0aGFuIHRoZSBMQVNTTyBtb2RlbCBvZiA0Ni4gVGh1cywgcmFuZG9tIGZvcmVzdCBtb2RlbCBwZXJmb3JtcyBiZXR0ZXIuIAoKYGBge3J9CnNldC5zZWVkKDEyMTEpICMgZm9yIHJlcHJvZHVjaWJpbGl0eQpkYXRhX2N2IDwtIHZmb2xkX2N2KGRhdGFfdHJhaW5pbmcsIHYgPSA1KQoKbWV0cmljIDwtIG1ldHJpY19zZXQocm1zZSkKY3RybF9yZXMgPC0gY29udHJvbF9zdGFja19yZXNhbXBsZXMoKQoKcmFuZ2VyX2N2IDwtIHJhbmdlcl93b3JrZmxvdyAlPiUgCiAgZml0X3Jlc2FtcGxlcyhkYXRhX2N2LCAKICAgICAgICAgICAgICAgIG1ldHJpY3MgPSBtZXRyaWMsCiAgICAgICAgICAgICAgICBjb250cm9sID0gY3RybF9yZXMpCgojIEV2YWx1YXRpb24gbWV0cmljcyBhdmVyYWdlZCBvdmVyIGFsbCBmb2xkczoKY29sbGVjdF9tZXRyaWNzKHJhbmdlcl9jdikKYGBgCgpXZSBhbHNvIHBsb3QgYSBncmFwaCBzaG93aW5nIHRoZSBhY3R1YWwgcmV0dXJuIHZzIHByZWRpY3RlZCByZXR1cm4uIAoKYGBge3J9CnJhbmdlcl9wcmVkaWN0aW9uIDwtIHByZWRpY3QoCiAgcmFuZ2VyX2ZpdCwKICBuZXdfZGF0YSA9IGRhdGFfdHJhaW5pbmcpCgpyYW5nZXJfdHJhaW5pbmdfcHJlZDwtZGF0YV90cmFpbmluZyAlPiUgCiAgbXV0YXRlKC5wcmVkID0gcmFuZ2VyX3ByZWRpY3Rpb24kLnByZWQpCiAgCiAgCnJhbmdlcl90cmFpbmluZ19wcmVkICU+JQogIGdncGxvdChhZXMoeCA9IFBST0ZJVCwKICAgICAgICAgICAgIHkgPSAucHJlZCkpICsKICBnZW9tX3BvaW50KGFscGhhID0gLjUsCiAgICAgICAgICAgICBzaXplID0gLjUpICsKICBnZW9tX3Ntb290aChzZSA9IEZBTFNFKSArCiAgZ2VvbV9hYmxpbmUoc2xvcGUgPSAxLAogICAgICAgICAgICAgIGludGVyY2VwdCA9IDAsCiAgICAgICAgICAgICAgY29sb3IgPSAiZGFya3JlZCIpICsKICBsYWJzKHggPSAiQWN0dWFsIFJldHVybiIsCiAgICAgICB5ID0gIlByZWRpY3RlZCBSZXR1cm4iKQoKYGBgCgojIyMjIFByZWRpY2l0b24gcHJlY2lzaW9uIChUZXN0aW5nIGRhdGEpCgpUaGUgdGFibGUgYmVsb3cgc2hvd3MgdGhlIHJtc2Ugb24gdGVzdGluZyBkYXRhLiBJdCBpcyBhIGJpdCBoaWdoZXIgYnV0IGZhaXJseSBzaW1pbGFyIHRvIHRoZSBlcnJvciBvbiB0cmFpbmluZyBkYXRhIHNvIHdlIGNvdWxkIHNheSB0aGF0IG91ciBtb2RlbCBkaWQgbm90IG92ZXJmaXQuIAoKYGBge3J9CnNldC5zZWVkKDEyMTEpICMgZm9yIHJlcHJvZHVjaWJpbGl0eQpkYXRhX2N2IDwtIHZmb2xkX2N2KGRhdGFfdGVzdGluZywgdiA9IDUpCgptZXRyaWMgPC0gbWV0cmljX3NldChybXNlKQpjdHJsX3JlcyA8LSBjb250cm9sX3N0YWNrX3Jlc2FtcGxlcygpCgpyYW5nZXJfY3YgPC0gcmFuZ2VyX3dvcmtmbG93ICU+JSAKICBmaXRfcmVzYW1wbGVzKGRhdGFfY3YsIAogICAgICAgICAgICAgICAgbWV0cmljcyA9IG1ldHJpYywKICAgICAgICAgICAgICAgIGNvbnRyb2wgPSBjdHJsX3JlcykKCiMgRXZhbHVhdGlvbiBtZXRyaWNzIGF2ZXJhZ2VkIG92ZXIgYWxsIGZvbGRzOgpjb2xsZWN0X21ldHJpY3MocmFuZ2VyX2N2KQpgYGAKCkJlbG93IGlzIHRoZSBncmFwaCBzaG93aW5nIHRoZSBhY3R1YWwgcmV0dXJuIHZzLiBwcmVkaWN0ZWQgcmV0dXJuIG9uIHRlc3RpbmcgZGF0YS4gCgpgYGB7cn0KcmFuZ2VyX3Rlc3RfcHJlZGljdGlvbiA8LSBwcmVkaWN0KAogIHJhbmdlcl9maXQsCiAgbmV3X2RhdGEgPSBkYXRhX3Rlc3RpbmcpCgpyYW5nZXJfdGVzdF9wcmVkPC1kYXRhX3Rlc3RpbmcgJT4lIAogIG11dGF0ZSgucHJlZCA9IHJhbmdlcl90ZXN0X3ByZWRpY3Rpb24kLnByZWQpCiAgCiAgCnJhbmdlcl90ZXN0X3ByZWQgJT4lCiAgZ2dwbG90KGFlcyh4ID0gUFJPRklULAogICAgICAgICAgICAgeSA9IC5wcmVkKSkgKwogIGdlb21fcG9pbnQoYWxwaGEgPSAuNSwKICAgICAgICAgICAgIHNpemUgPSAuNSkgKwogIGdlb21fc21vb3RoKHNlID0gRkFMU0UpICsKICBnZW9tX2FibGluZShzbG9wZSA9IDEsCiAgICAgICAgICAgICAgaW50ZXJjZXB0ID0gMCwKICAgICAgICAgICAgICBjb2xvciA9ICJkYXJrcmVkIikgKwogIGxhYnMoeCA9ICJBY3R1YWwgUmV0dXJuIiwKICAgICAgIHkgPSAiUHJlZGljdGVkIFJldHVybiIpCgpgYGAKCiMjIyBJbnRlcnByZXRhYmxlIE1hY2hpbmUgTGVhcm5pbmcgCgpCYXNlZCBvbiB0aGUgYm94LXBsb3QgYW5kIHRoZSBoaXN0b2dyYW0gYmVsb3csIHRoZSByZXNpZHVhbHMgbW9zdGx5IGxpZSBiZXR3ZWVuIC01MCB0byA1MC4gQnV0IHRoZXJlIGFyZSBzb21lIGEgZmV3IG91dGxpbmVycyB0aGF0IGNhbiBnbyB1cCB0byA0MDAuIAoKYGBge3J9CnJmX2V4cGxhaW4gPC0gCiAgZXhwbGFpbl90aWR5bW9kZWxzKAogICAgbW9kZWwgPSByYW5nZXJfZml0LAogICAgZGF0YSA9IGRhdGFfdHJhaW5pbmcgJT4lIHNlbGVjdCgtUFJPRklUKSwgCiAgICB5ID0gZGF0YV90cmFpbmluZyAlPiUgIHB1bGwoUFJPRklUKSwKICAgIGxhYmVsID0gInJmIgogICkKcmZfbW9kX3BlcmYgPC0gIG1vZGVsX3BlcmZvcm1hbmNlKHJmX2V4cGxhaW4pCgpoaXN0X3Bsb3QgPC0gCiAgcGxvdChyZl9tb2RfcGVyZiwgCiAgICAgICBnZW9tID0gImhpc3RvZ3JhbSIpCmJveF9wbG90IDwtCiAgcGxvdChyZl9tb2RfcGVyZiwgCiAgICAgICBnZW9tID0gImJveHBsb3QiKQoKaGlzdF9wbG90CmJveF9wbG90CmBgYAoKQWNjb3JkaW5nIHRvIHRoZSBmZWF0dXJlIGltcG9ydGFuY2UgYmFyIGNoYXJ0IGJlbG93LCB3ZSBjYW4gc2VlIHRoYXQgdGhlIHRvcCB0aHJlZSBpbXBvcnRhbnQgZmVhdHVyZXMgYXJlIG1hcmtldCBjYXAsIGVhcm5pbmdzLCBNMVNMX1BDMSwgYW5kIEdEUF9QQzEuIAoKYGBge3J9CnNldC5zZWVkKDEwKSAjc2luY2Ugd2UgYXJlIHNhbXBsaW5nICYgcGVybXV0aW5nLCB3ZSBzZXQgYSBzZWVkIHNvIHdlIGNhbiByZXBsaWNhdGUgdGhlIHJlc3VsdHMKcmZfdmFyX2ltcCA8LSAKICBtb2RlbF9wYXJ0cygKICAgIHJmX2V4cGxhaW4KICAgICkKcGxvdChyZl92YXJfaW1wLCBzaG93X2JveHBsb3RzID0gVFJVRSkKYGBgCgpBZnRlciBjcmVhdGluZyB0d28gbW9kZWxzLCB3ZSB0aGVuIG1vdmUgb24gdG8gdGhlIGZpbmFsIG1ldGhvZDogc3RhY2tpbmcuIEZvciB0aGUgc3RhY2tpbmcgbW9kZWwsIHdlIGFsc28gYWRkIG9uZSBtb3JlIG1vZGVsOiBLTk4gdG8gaW1wcm92ZSB0aGUgYWNjdXJhY3kgcmF0ZSBvZiB0aGUgbW9kZWwuCgojIyBTdGFja2luZyBNb2RlbAoKIyMjIFJhbmRvbSBGb3Jlc3QgQ2FuZGlkYXRlIAoKYGBge3J9CnJhbmdlcl9yZWNpcGUgPC0gCiAgcmVjaXBlKGZvcm11bGEgPSBQUk9GSVQgfiAuLCAKICAgICAgICAgZGF0YSA9IGRhdGFfdHJhaW5pbmcpICU+JSAKICAjIE1ha2UgdGhlc2UgZXZhbHVhdGl2ZSB2YXJpYWJsZXMsIG5vdCBpbmNsdWRlZCBpbiBtb2RlbGluZwogIHVwZGF0ZV9yb2xlKGFsbF9vZihjKCJZRUFSIiwgIk5hbWUiLCAiU2VjdG9yIikpLAogICAgICAgICAgICAgIG5ld19yb2xlID0gImV2YWx1YXRpdmUiKQoKcmFuZ2VyX3NwZWMgPC0gCiAgcmFuZF9mb3Jlc3QobXRyeSA9IDYsIAogICAgICAgICAgICAgIG1pbl9uID0gMTAsIAogICAgICAgICAgICAgIHRyZWVzID0gMjAwKSAlPiUgCiAgc2V0X21vZGUoInJlZ3Jlc3Npb24iKSAlPiUgCiAgc2V0X2VuZ2luZSgicmFuZ2VyIikKCnJhbmdlcl93b3JrZmxvdyA8LSAKICB3b3JrZmxvdygpICU+JSAKICBhZGRfcmVjaXBlKHJhbmdlcl9yZWNpcGUpICU+JSAKICBhZGRfbW9kZWwocmFuZ2VyX3NwZWMpIAoKcmFuZ2VyX2ZpdCA8LSByYW5nZXJfd29ya2Zsb3cgJT4lIAogIGZpdChkYXRhX3RyYWluaW5nKQoKc2V0LnNlZWQoMTIxMSkgIyBmb3IgcmVwcm9kdWNpYmlsaXR5CmZpbmFsX2RhdGFfY3YgPC0gdmZvbGRfY3YoZGF0YV90cmFpbmluZywgdiA9IDUpCgptZXRyaWMgPC0gbWV0cmljX3NldChybXNlKQpjdHJsX3JlcyA8LSBjb250cm9sX3N0YWNrX3Jlc2FtcGxlcygpCgpyYW5nZXJfY3YgPC0gcmFuZ2VyX3dvcmtmbG93ICU+JSAKICBmaXRfcmVzYW1wbGVzKGZpbmFsX2RhdGFfY3YsIAogICAgICAgICAgICAgICAgbWV0cmljcyA9IG1ldHJpYywKICAgICAgICAgICAgICAgIGNvbnRyb2wgPSBjdHJsX3JlcykKYGBgCgojIyMgTEFTU08gQ2FuZGlhdGUKCmBgYHtyfQojIGxhc3NvIHJlY2lwZSBhbmQgdHJhbnNmb3JtYXRpb24gc3RlcHMKbGFzc29fZmluYWxfZGF0YV9yZWNpcGUgPC0gcmVjaXBlKFBST0ZJVCB+IC4sIAogICAgICAgICAgICAgICAgICAgICAgIGRhdGEgPSBkYXRhX3RyYWluaW5nKSAlPiUgCiAgI3N0ZXBfcm0oTmFtZSwgU2VjdG9yLCBZRUFSLCBDT01QQU5ZKSAlPiUKICB1cGRhdGVfcm9sZShhbGxfb2YoYygiTmFtZSIsCiAgICAgICAgICAgICAgICAgICAgICAgIlNlY3RvciIsCiAgICAgICAgICAgICAgICAgICAgICAgIllFQVIiKSksCiAgICAgICAgICAgICAgbmV3X3JvbGUgPSAiZXZhbHVhdGl2ZSIpICU+JSAKICBzdGVwX2R1bW15KGFsbF9ub21pbmFsKCksIAogICAgICAgICAgICAgLWFsbF9vdXRjb21lcygpLCAKICAgICAgICAgICAgIC1oYXNfcm9sZShtYXRjaCA9ICJldmFsdWF0aXZlIikpICU+JSAKICBzdGVwX25vcm1hbGl6ZShhbGxfcHJlZGljdG9ycygpLCAKICAgICAgICAgICAgICAgICAtYWxsX25vbWluYWwoKSkKCiNkZWZpbmUgbGFzc28gbW9kZWwKbGFzc29fbW9kIDwtIAogIGxpbmVhcl9yZWcobWl4dHVyZSA9IDEpICU+JSAKICBzZXRfZW5naW5lKCJnbG1uZXQiKSAlPiUgCiAgc2V0X2FyZ3MocGVuYWx0eSA9IHR1bmUoKSkgJT4lIAogIHNldF9tb2RlKCJyZWdyZXNzaW9uIikKCiMgY3JlYXRlIHdvcmtmbG93Cmxhc3NvX3dmIDwtIAogIHdvcmtmbG93KCkgJT4lIAogIGFkZF9yZWNpcGUobGFzc29fZmluYWxfZGF0YV9yZWNpcGUpICU+JSAKICBhZGRfbW9kZWwobGFzc29fbW9kKQoKIyBwZW5hbHR5IGdyaWQgLSBjaGFuZ2VkIHRvIDEwIGxldmVscwpwZW5hbHR5X2dyaWQgPC0gZ3JpZF9yZWd1bGFyKHBlbmFsdHkoKSwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICBsZXZlbHMgPSAxMCkKCiMgYWRkIGN0cmxfZ3JpZCAtIGFzc3VyZXMgcHJlZGljdGlvbnMgYW5kIHdvcmtmbG93cyBhcmUgc2F2ZWQKY3RybF9ncmlkIDwtIGNvbnRyb2xfc3RhY2tfZ3JpZCgpCgojIHR1bmUgdGhlIG1vZGVsIHVzaW5nIHRoZSBzYW1lIGN2IHNhbXBsZXMgYXMgcmFuZG9tIGZvcmVzdAoKbGFzc29fdHVuZSA8LSAKICBsYXNzb193ZiAlPiUgCiAgdHVuZV9ncmlkKAogICAgcmVzYW1wbGVzID0gZmluYWxfZGF0YV9jdiwKICAgIGdyaWQgPSBwZW5hbHR5X2dyaWQsCiAgICBtZXRyaWNzID0gbWV0cmljLAogICAgY29udHJvbCA9IGN0cmxfZ3JpZAogICAgKQpgYGAKCiMjIyBLTk4gQ2FuZGlkYXRlCgpgYGB7cn0KIyBjcmVhdGUgYSBtb2RlbCBkZWZpbml0aW9uCmtubl9tb2QgPC0KICBuZWFyZXN0X25laWdoYm9yKAogICAgbmVpZ2hib3JzID0gdHVuZSgiayIpCiAgKSAlPiUKICBzZXRfZW5naW5lKCJra25uIikgJT4lIAogIHNldF9tb2RlKCJyZWdyZXNzaW9uIikKCiMgY3JlYXRlIHRoZSB3b3JrZmxvdwprbm5fd2YgPC0gCiAgd29ya2Zsb3coKSAlPiUgCiAgYWRkX21vZGVsKGtubl9tb2QpICU+JQogIGFkZF9yZWNpcGUobGFzc29fZmluYWxfZGF0YV9yZWNpcGUpCgojIHR1bmUgaXQgdXNpbmcgNCB0dW5pbmcgcGFyYW1ldGVycwprbm5fdHVuZSA8LSAKICBrbm5fd2YgJT4lIAogIHR1bmVfZ3JpZCgKICAgIGZpbmFsX2RhdGFfY3YsCiAgICBtZXRyaWNzID0gbWV0cmljLAogICAgZ3JpZCA9IDQsCiAgICBjb250cm9sID0gY3RybF9ncmlkCiAgKQpgYGAKCgojIyMgU3RhY2tpbmcgYWxsIGNhbmRpZGF0ZXMKQnkgc2hvd2luZyBhbmQgdmlzdWFsaXplIHRoZSBtZW1iZXJzIGluIG91ciBzdGFja2luZyBtb2RlbCwgd2UgY291bGQgc2VlIHRoYXQgcmFuZG9tIGZvcmVzdCBtb2RlbCBjb250cmlidXRlIHRoZSBtb3N0LiAKCmBgYHtyfQpmaW5hbF9kYXRhX3N0YWNrIDwtIAogIHN0YWNrcygpICU+JSAKICBhZGRfY2FuZGlkYXRlcyhyYW5nZXJfY3YpICU+JSAKICBhZGRfY2FuZGlkYXRlcyhsYXNzb190dW5lKSAlPiUgCiAgYWRkX2NhbmRpZGF0ZXMoa25uX3R1bmUpCgpmaW5hbF9kYXRhX2JsZW5kIDwtIAogIGZpbmFsX2RhdGFfc3RhY2sgJT4lIAogIGJsZW5kX3ByZWRpY3Rpb25zKCkKCmZpbmFsX2RhdGFfYmxlbmQKYGBgCgpgYGB7cn0KYXV0b3Bsb3QoZmluYWxfZGF0YV9ibGVuZCkKYGBgCgpgYGB7cn0KZmluYWxfZGF0YV9ibGVuZCRtZXRyaWNzICU+JSAKICBmaWx0ZXIoLm1ldHJpYyA9PSAicm1zZSIpICU+JSAKICBzdW1tYXJpc2UobWVhbl9ybXNlID0gbWVhbihtZWFuKSkKYGBgCgoKCmBgYHtyfQpmaW5hbF9kYXRhX2ZpbmFsX3N0YWNrIDwtIGZpbmFsX2RhdGFfYmxlbmQgJT4lIAogIGZpdF9tZW1iZXJzKCkKYGBgCgpUYWJsZSBiZWxvdyBzaG93cyB0aGUgdG9wMTAgcHJlZGljdGlvbiB3aGVuIHdlIGFwcGxpZWQgdGhlIG1vZGVsIHRvIHRlc3RpbmcgZGF0YS4gSGVyZSwgd2UgY2FuIHNlZSB0aGF0IHdoZW4gdGhlIG1vZGVsIHByZWRpY3RzIGNvbXBhbnkgd2l0aCBoaWdoIHJhdGUgb2YgcmV0dXJuLCB0aGUgYWN0dWFsIHJldHVybiBpcyBoaWdoIGFzIHdlbGwuIApgYGB7cn0KZmluYWxfZGF0YV9maW5hbF9zdGFjayAlPiUgCiAgcHJlZGljdChuZXdfZGF0YSA9IGRhdGFfdGVzdGluZykgJT4lIAogIGJpbmRfY29scyhkYXRhX3Rlc3RpbmcpICU+JSAKICBzZWxlY3QoTmFtZSwgLnByZWQsIFBST0ZJVCkgJT4lIAogIGFycmFuZ2UoZGVzYygucHJlZCkpICU+JSAKICBoZWFkKDEwKQpgYGAKCgoKIyMjIENvbXBhcmlzb24gb2YgdGhlIHRocmVlIG1vZGVscwoKV2l0aCB0aGUgdGhyZWUgbW9kZWxzLCB3ZSB0aGVuIG1vdmUgb24gdG8gc2VlIHdoaWNoIG1vZGVsIHBlcmZvcm1zIHRoZSBiZXN0OiAKCiMjIyMgTGFzc28gbW9kZWwKYGBge3J9CmRhdGFfZnJhbWUocmV0dXJuX2xtX3R1bmUgJT4lCiAgc2VsZWN0KGlkLCAubWV0cmljcykgJT4lCiAgdW5uZXN0KC5tZXRyaWNzKSAlPiUKICBmaWx0ZXIoLm1ldHJpYyA9PSAicnNxIikgJT4lIAogIGZpbHRlciguY29uZmlnID09ICJQcmVwcm9jZXNzb3IxX01vZGVsMDgiKSAlPiUgCiAgc3VtbWFyaXNlKG1lYW5fcnNxID0gbWVhbiguZXN0aW1hdGUpKSwKCnJldHVybl9sbV90dW5lICU+JQogIHNlbGVjdChpZCwgLm1ldHJpY3MpICU+JQogIHVubmVzdCgubWV0cmljcykgJT4lCiAgZmlsdGVyKC5tZXRyaWMgPT0gInJtc2UiKSAlPiUgCiAgZmlsdGVyKC5jb25maWcgPT0gIlByZXByb2Nlc3NvcjFfTW9kZWwwOCIpICU+JSAKICBzdW1tYXJpc2UobWVhbl9ybXNlID0gbWVhbiguZXN0aW1hdGUpKSkKYGBgCgojIyMjIFJhbmRvbSBGb3Jlc3QgTW9kZWwKCmBgYHtyfQojT09CIFJNU0UKZGF0YS5mcmFtZShtZWFuX3Jtc2UgPSBzcXJ0KHJhbmdlcl9maXQkZml0JGZpdCRmaXQkcHJlZGljdGlvbi5lcnJvciksCiMgUiBzcXVhcmVkCm1lYW5fcnNxID0gcmFuZ2VyX2ZpdCRmaXQkZml0JGZpdCRyLnNxdWFyZWQpCmBgYAoKCiMjIyMgU3RhY2tpbmcgTW9kZWwgCgpgYGB7cn0KIyBTdGFja2luZyBtb2RlbDogCmRhdGEuZnJhbWUoZmluYWxfZGF0YV9ibGVuZCRtZXRyaWNzICU+JSAKICBmaWx0ZXIoLm1ldHJpYyA9PSAicm1zZSIpICU+JSAKICBzdW1tYXJpc2UobWVhbl9ybXNlID0gbWVhbihtZWFuKSksCgpmaW5hbF9kYXRhX2JsZW5kJG1ldHJpY3MgJT4lIAogIGZpbHRlcigubWV0cmljID09ICJyc3EiKSAlPiUgCiAgc3VtbWFyaXNlKG1lYW5fcnNxID0gbWVhbihtZWFuKSkpCmBgYApIZXJlLCB3ZSBjYW4gc2VlIHRoYXQgY29tcGFyZWQgdG8gdGhlIHRocmVlIG1vZGVscywgZXZlbiB0aG91Z2ggUmFuZG9tIEZvcmVzdCBwZXJmb3JtcyBiZXR0ZXIgdGhhbiB0aGUgc3RhY2tpbmcgbW9kZWwsIHN0YWNraW5nIG1vZGVsIHVzZSBmZWF0dXJlcyBpbiB0aGUgUmFuZG9tIEZvcmVzdCBhbG9uZyB3aXRoIGFkZGl0aW9uYWwgZmVhdHVyZXMgZnJvbSBLTk4gYW5kIGxhc3NvLiBXaXRoIHRoYXQgcmVhc29uLCB3ZSB3aWxsIGNob29zZSBzdGFja2luZyBtb2RlbCBhcyBvdXIgbW9kZWwgY2hvaWNlLiAKCiMjIFN0b2NrIFJldHVybiBQcmVkaWN0aW9uIGZvciAyMDIxCgpBZnRlciBwaWNraW5nIG91ciBtb2RlbCwgd2UgdGhlbiBtb3ZlIG9uIHRvIHVzZSB0aGUgbW9kZWwgdG8gcHJlZGljdCB0aGUgcG90ZW50aWFsIHByb2ZpdCBmb3IgMjAyMTogCgpgYGB7cn0Kc2V0LnNlZWQoNDU2KQp5dGQgPSBjKDgwLjMwLCAzOS43NywgLTguMjYsIDExLjI5LCAyMi4wOCwgMTMyLjUxLCAxMzcuMDQsIC00LjIyLCAzOS43NywgMjAuNzYsIDI2LjQxLCAyNi4zNCwgNDEuNTAsIDE3OC40MywgMTguNzAsIDg3LjI5LCAtNi4xNiwgNTIuNTgsIDguNzAsIC0xMy43MCkKc2V0LnNlZWQoNDU2KQpwcmVkXzIwMjE8LWZpbmFsX2RhdGFfZmluYWxfc3RhY2sgJT4lIAogIHByZWRpY3QobmV3X2RhdGEgPSBmaW5hbF9kYXRhXzIwMjEpICU+JSAKICBiaW5kX2NvbHMoZmluYWxfZGF0YV8yMDIxKSAlPiUgCiAgc2VsZWN0KE5hbWUsIC5wcmVkKSAlPiUgCiAgYXJyYW5nZShkZXNjKC5wcmVkKSkgJT4lIAogIGhlYWQoMjApICU+JSAKICBtdXRhdGUoYWN0dWFsX3l0ZCA9IHl0ZCkgCnByZWRfMjAyMSAlPiUgCiAga2JsKCkgJT4lIAogIGthYmxlX3N0eWxpbmcoYm9vdHN0cmFwX29wdGlvbnMgPSBjKCJzdHJpcGVkIiwgImJvcmRlcmVkIiwgImhvdmVyIiwgImNvbmRlbnNlZCIpKSAlPiUgCiAgY29sdW1uX3NwZWMoYygxLDMpLCBjb2xvciA9IGlmZWxzZShwcmVkXzIwMjEkYWN0dWFsX3l0ZCA8IDAsICJyZWQiLCAiZGFya2dyZWVuIikpICU+JSAKICBzY3JvbGxfYm94KHdpZHRoID0gIjEwMCUiLCBoZWlnaHQgPSAiNTAwcHgiKSAKYGBgCgojIyBDb25jbHVzaW9uIAoKVGhlIHdlYWtuZXNzIG9mIG91ciBtb2RlbCBpcyB0aGF0IHRoZSBhdmVyYWdlIGVycm9yIG9mIHByZWRpY3Rpb24gaXMgc3RpbGwgdmVyeSBoaWdoIHNvIGlmIG9uZSB3YW50cyB0byBwcmVkaWN0IHRoZSBleGFjdCByZXR1cm4sIG91ciBtb2RlbCB3b24ndCBiZSBpZGVhbC4gQnV0IHRoZSBzdHJlbmd0aCBvZiBvdXIgbW9kZWwgaXMgdGhhdCBmb3Igc3RvY2tzIHdpdGggZXh0cmVtZWx5IGhpZ2ggcmV0dXJucywgZXZlbiB0aG91Z2ggdGhlIHByZWRpY3Rpb24gbWlnaHQgbm90IGJlIHRoYXQgcHJlY2lzZSwgaGlnaGx5IGxpa2VseSwgb3VyIG1vZGVsIHdpbGwgcHJlZGljdCBwb3NpdGl2ZSByZXR1cm5zLiBUaGF0IG1lYW5zLCBpbiByZWFsIGxpZmUsIGlmIHdlIGNob29zZSB0aGUgdG9wIHN0b2NrcyB0byBpbnZlc3QgaW4gYmFzZWQgb24gb3VyIHByZWRpY3Rpb24sIGl0IGlzIGxlc3MgbGlrZWx5IHdlIGFyZSBnb2luZyB0byBsb3NlIG1vbmV5LiBBbm90aGVyIHN0cmVuZ3RoIG9mIHVzaW5nIGEgbW9kZWwgdG8gaGVscCB3aXRoIGludmVzdG1lbnQgaXMgdGhhdCBpdCBleGNsdWRlcyBvdXIgc3ViamVjdGl2ZSBmZWVsaW5ncy4gCgpUbyBtYWtlIHRoZSBtb2RlbCBiZXR0ZXIsIHdlIGNvdWxkIGRvIG1vcmUgcmVzZWFyY2ggYW5kIGFkZCBtb3JlIHJlZ3Jlc3NvcnMuIEZvciBleGFtcGxlLCBzb21lIGZpbmFuY2lhbCBpbmRpY2F0b3JzIHRoYXQgYXJlIGltcG9ydGFudCBmb3IgdmFsdWUgaW52ZXN0aW5nIGFyZSBub3QgcmVmbGVjdGVkIGluIG91ciBtb2RlbCBkdWUgdG8gdGhlIGxhY2sgb2YgZGF0YS4gIEV4YW1wbGVzIG9mIHRob3NlIHZhcmlhYmxlcyBpbmNsdWRlIFByaWNlIHRvIFNhbGVzLCBQcmljZSB0byBDYXNoIEZsb3csIGFuZCBQcmljZSB0byBCb29rLiBBbHNvLCBzaW5jZSB3ZSBhcmUgY29uY2VybmVkIGFib3V0IHRoZSBsb25nLXRlcm0gcmV0dXJuIGFuZCBhbGwgZnVuZGFtZW50YWwgZmFjdG9ycyB1c3VhbGx5IHRha2UgbG9uZ2VyIHRvIGFmZmVjdCB0aGUgZmlybXMsIGl0J3MgcHJvYmFibHkgaGVscGZ1bCB0byBkbyByZXR1cm4gaW4gMiB5ZWFycyBvciAzIHllYXJzIG9yIGluY2x1ZGUgbGFnIG9mIHNvbWUgdmFyaWFibGVzIGluIG91ciBtb2RlbC4gIAoKCg==